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
1 change: 1 addition & 0 deletions src/aks-preview/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ To release a new version, please select a new version number (usually plus 1 to

Pending
+++++++
* `az aks maintenancewindow`: Add CRUD commands (`create`, `show`, `list`, `update`, `delete`, `wait`) for the new MaintenanceWindow peer ARM resource. Available with API version `2026-04-02-preview`. Requires the `Microsoft.ContainerService/AKSSharedMaintenanceWindowPreview` feature to be registered on the subscription (auto-approval).
* `az aks bastion`: Fix failure when the bastion host is in a different subscription than the cluster by using the subscription from the bastion resource ID for the internal `az network bastion tunnel` command.
* Set `principalType` when creating role assignments to avoid `PrincipalNotFound` failures caused by Microsoft Entra ID replication delay for freshly created identities.

Expand Down
4 changes: 4 additions & 0 deletions src/aks-preview/azext_aks_preview/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ def cf_maintenance_configurations(cli_ctx, *_):
return get_container_service_client(cli_ctx).maintenance_configurations


def cf_maintenance_windows(cli_ctx, *_):
return get_container_service_client(cli_ctx).maintenance_windows


def cf_nodepool_snapshots(cli_ctx, *_):
return get_container_service_client(cli_ctx).snapshots

Expand Down
181 changes: 181 additions & 0 deletions src/aks-preview/azext_aks_preview/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1979,6 +1979,187 @@
}
"""

helps['aks maintenancewindow'] = """
type: group
short-summary: Commands to manage MaintenanceWindow peer ARM resources.
long-summary: |
MaintenanceWindow is a resource-group-scoped ARM resource that defines
a reusable maintenance schedule. Users can link this resource to a
pre-existing maintenanceConfiguration via `maintenanceWindowId`, so
multiple managedCluster maintenance configurations share a single
MaintenanceWindow and the schedule lives in one place.

Requires the Microsoft.ContainerService/AKSSharedMaintenanceWindowPreview
feature to be registered on the subscription. Auto-approval is enabled:
`az feature register --namespace Microsoft.ContainerService --name AKSSharedMaintenanceWindowPreview`
"""

helps['aks maintenancewindow show'] = """
type: command
short-summary: Show a MaintenanceWindow.
"""

helps['aks maintenancewindow list'] = """
type: command
short-summary: List MaintenanceWindows in a resource group, or across the subscription if --resource-group is omitted.
"""

helps['aks maintenancewindow delete'] = """
type: command
short-summary: Delete a MaintenanceWindow.
"""

helps['aks maintenancewindow create'] = """
type: command
short-summary: Create a MaintenanceWindow.
long-summary: |
Requires the Microsoft.ContainerService/AKSSharedMaintenanceWindowPreview
feature to be registered on the subscription. Register once (auto-approved):
`az feature register --namespace Microsoft.ContainerService --name AKSSharedMaintenanceWindowPreview`
parameters:
- name: --location -l
type: string
short-summary: Location for the MaintenanceWindow. Defaults to the resource group's location when omitted.
- name: --schedule-type
type: string
short-summary: Recurrence type. One of Daily, Weekly, AbsoluteMonthly or RelativeMonthly.
- name: --start-date
type: string
short-summary: The date the maintenance window activates. e.g. 2026-06-01. If not specified, the window activates immediately.
- name: --start-time
type: string
short-summary: The start time of the maintenance window. Accepted values are from '00:00' to '23:59'. --utc-offset applies to this field.
- name: --duration
type: int
short-summary: The length of the maintenance window in hours. Range 4-24.
- name: --utc-offset
type: string
short-summary: The UTC offset in format +/-HH:mm. For example, '+05:30' for IST or '-07:00' for PST. Default is '+00:00'.
- name: --interval-days
type: int
short-summary: Number of days between occurrences. Daily schedule only.
- name: --interval-weeks
type: int
short-summary: Number of weeks between occurrences. Weekly schedule only.
- name: --interval-months
type: int
short-summary: Number of months between occurrences. AbsoluteMonthly or RelativeMonthly schedules only.
- name: --day-of-week
type: string
short-summary: Day of week the maintenance occurs. e.g. Monday. Weekly or RelativeMonthly schedules only.
- name: --day-of-month
type: int
short-summary: Day of month the maintenance occurs. e.g. 15. AbsoluteMonthly schedule only.
- name: --week-index
type: string
short-summary: Instance of the day-of-week the maintenance occurs (First, Second, Third, Fourth, Last). RelativeMonthly schedule only.
- name: --config-file
type: string
short-summary: Path to a JSON file describing the MaintenanceWindow body. Use when you need to set fields without dedicated flags (e.g. notAllowedDates / blackout date ranges).
examples:
- name: Create a weekly Saturday window (the canonical "production weekends" shape).
text: |
az aks maintenancewindow create -g rg-maintenance -n production-weekends -l eastus \\
--schedule-type Weekly --day-of-week Saturday --interval-weeks 1 \\
--start-time "02:00" --duration 8 --utc-offset=-07:00 \\
--tags environment=production
- name: Create a daily window every 2 days from a specific start date.
text: |
az aks maintenancewindow create -g rg-maintenance -n nightly -l eastus \\
--schedule-type Daily --interval-days 2 \\
--start-date 2026-06-01 --start-time "00:00" --duration 6 --utc-offset=-08:00
- name: Create an absolute monthly window on the 15th.
text: |
az aks maintenancewindow create -g rg-maintenance -n monthly-15 -l eastus \\
--schedule-type AbsoluteMonthly --day-of-month 15 --interval-months 1 \\
--start-time "09:30" --duration 6 --utc-offset=+05:30
- name: Create a relative monthly window on the last Friday.
text: |
az aks maintenancewindow create -g rg-maintenance -n last-friday -l eastus \\
--schedule-type RelativeMonthly --day-of-week Friday --week-index Last --interval-months 1 \\
--start-time "01:00" --duration 4 --utc-offset=+00:00
- name: Create a weekly window with blackout dates via a config file.
text: |
az aks maintenancewindow create -g rg-maintenance -n weekly-with-holidays -l eastus \\
--config-file ./mw-body.json
The contents of mw-body.json:
{
"properties": {
"schedule": { "weekly": { "intervalWeeks": 1, "dayOfWeek": "Saturday" } },
"startTime": "02:00",
"durationHours": 8,
"utcOffset": "-07:00",
"notAllowedDates": [
{ "start": "2026-12-23", "end": "2027-01-05" },
{ "start": "2026-07-04", "end": "2026-07-05" }
]
}
}
"""

helps['aks maintenancewindow update'] = """
type: command
short-summary: Update a MaintenanceWindow.
long-summary: |
Read-modify-write semantics matching `az aks update` and
`az aks nodepool update`: the existing MW is fetched, your supplied
fields are merged onto it, and the resulting body is PUT back.
Fields you do not supply are preserved as-is (existing tags,
existing schedule scalars, existing blackout dates, etc.).

When you do supply a schedule arg (--schedule-type / --interval-* /
--day-of-week / --day-of-month / --week-index), the schedule shape
is re-built end-to-end from your args (you cannot partially mix
Daily + Weekly fields — pick one schedule type and supply its
required args).

--tags follows `aks update` convention: passing --tags replaces the
whole tags dictionary; omit --tags to keep the existing tags.

--location cannot be changed (TrackedResource semantics — ARM
rejects location changes on PUT). If supplied with a different
value than the existing location it is ignored with a warning.

--config-file: when supplied, the JSON file's `properties` block
replaces the existing `properties` wholesale. This is the only
way today to mutate notAllowedDates / blackout date ranges.
Individual schedule flags are ignored on this path; tags follow
the same `--tags` rule as without --config-file.
examples:
- name: Update only tags (existing schedule is preserved).
text: az aks maintenancewindow update -g rg-maintenance -n production-weekends --tags environment=staging
- name: Update only the schedule's day-of-week (existing duration / start-time / tags preserved).
text: |
az aks maintenancewindow update -g rg-maintenance -n production-weekends \\
--schedule-type Weekly --day-of-week Sunday --interval-weeks 1
- name: Change duration only (existing schedule shape preserved).
text: az aks maintenancewindow update -g rg-maintenance -n production-weekends --duration 6
- name: Replace blackout dates via a config file (preserves tags, location).
text: |
az aks maintenancewindow update -g rg-maintenance -n production-weekends --config-file ./mw-body.json
The contents of mw-body.json should include a complete properties block, e.g.:
{
"properties": {
"schedule": { "weekly": { "intervalWeeks": 1, "dayOfWeek": "Saturday" } },
"startTime": "02:00",
"durationHours": 8,
"utcOffset": "-07:00",
"notAllowedDates": [ { "start": "2026-12-23", "end": "2027-01-05" } ]
}
}
"""

helps['aks maintenancewindow wait'] = """
type: command
short-summary: Wait for a MaintenanceWindow to reach a desired state.
long-summary: |
If an operation on a MaintenanceWindow was interrupted or was started
with `--no-wait`, use this command to wait for it to complete.
examples:
- name: Wait for a MaintenanceWindow to be created.
text: az aks maintenancewindow wait -g rg-maintenance -n production-weekends --created
"""

helps['aks namespace'] = """
type: group
short-summary: Commands to manage namespace in managed Kubernetes cluster.
Expand Down
91 changes: 91 additions & 0 deletions src/aks-preview/azext_aks_preview/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@
validate_start_time,
validate_user,
validate_utc_offset,
validate_duration_hours,
validate_vm_set_type,
validate_vnet_subnet_id,
validate_force_upgrade_disable_and_enable_parameters,
Expand Down Expand Up @@ -2707,6 +2708,96 @@ def load_arguments(self, _):
"config_name", options_list=["--name", "-n"], help="The config name."
)

# -- aks maintenancewindow (peer ARM resource) -----------------------------
# --location/-l is auto-bound by the CLI core to the `location` kwarg on
# each handler (same pattern as `aks_nodepool_snapshot_create`), so no
# explicit c.argument("location", ...) is needed here.
with self.argument_context("aks maintenancewindow") as c:
c.argument(
"maintenance_window_name",
options_list=["--name", "-n"],
help="The maintenance window name.",
)

for scope in [
"aks maintenancewindow create",
"aks maintenancewindow update",
]:
with self.argument_context(scope) as c:
c.argument("tags", tags_type)
c.argument(
"config_file",
help=(
"Path to a JSON file describing the MaintenanceWindow body. "
"When supplied, the JSON wholly defines the resource on create, "
"or replaces the existing `properties` block on update. "
"This is the only path that can set fields without dedicated "
"flags (notAllowedDates, etc.)."
),
)
c.argument(
"schedule_type",
arg_type=get_enum_type(schedule_types),
help="Recurrence type: Daily, Weekly, AbsoluteMonthly or RelativeMonthly.",
)
c.argument(
"interval_days",
type=int,
help="The number of days between each set of occurrences for Daily schedule.",
)
c.argument(
"interval_weeks",
type=int,
help="The number of weeks between each set of occurrences for Weekly schedule.",
)
c.argument(
"interval_months",
type=int,
help=(
"The number of months between each set of occurrences for AbsoluteMonthly "
"or RelativeMonthly schedule."
),
)
c.argument(
"day_of_week",
help="Day of week the maintenance occurs for Weekly or RelativeMonthly schedule.",
)
c.argument(
"day_of_month",
type=int,
help="Day of month the maintenance occurs for AbsoluteMonthly schedule.",
)
c.argument(
"week_index",
arg_type=get_enum_type(week_indexes),
help=(
"Instance of the weekday specified in --day-of-week the maintenance occurs "
"for RelativeMonthly schedule."
),
)
c.argument(
"duration_hours",
options_list=["--duration"],
type=int,
validator=validate_duration_hours,
help="The length of the maintenance window in hours. Range 4-24.",
)
c.argument(
"utc_offset",
validator=validate_utc_offset,
help="The UTC offset in format +/-HH:mm. e.g. -08:00 or +05:30.",
)
c.argument(
"start_date",
validator=validate_start_date,
help="The date the maintenance window activates. e.g. 2026-06-01.",
)
c.argument(
"start_time",
validator=validate_start_time,
help="The start time of the maintenance window. e.g. 09:30.",
)

with self.argument_context("aks addon show") as c:
c.argument("addon", options_list=["--addon", "-a"], validator=validate_addon)

Expand Down
15 changes: 15 additions & 0 deletions src/aks-preview/azext_aks_preview/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,21 @@ def validate_start_time(namespace):
raise InvalidArgumentValueError('--start-time must be in format "HH:mm". For example, "09:30" and "17:00".')


def validate_duration_hours(namespace):
"""Validates --duration for aks maintenancewindow / maintenanceconfiguration commands.

The RP enforces 4-24 hours; fail locally with a clear argparse error
instead of letting the user discover the constraint via an opaque
server-side 400.
"""
if namespace.duration_hours is None:
return
if not isinstance(namespace.duration_hours, int) or namespace.duration_hours < 4 or namespace.duration_hours > 24:
raise InvalidArgumentValueError(
f'--duration must be an integer between 4 and 24 hours (inclusive). Got: {namespace.duration_hours}.'
)


def validate_os_sku(namespace):
os_sku = namespace.os_sku
if os_sku in [CONST_OS_SKU_MARINER, CONST_OS_SKU_CBLMARINER]:
Expand Down
23 changes: 23 additions & 0 deletions src/aks-preview/azext_aks_preview/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
cf_agent_pools,
cf_managed_namespaces,
cf_maintenance_configurations,
cf_maintenance_windows,
cf_managed_clusters,
cf_mc_snapshots,
cf_nodepool_snapshots,
Expand Down Expand Up @@ -136,6 +137,12 @@ def load_command_table(self, _):
client_factory=cf_maintenance_configurations,
)

maintenance_window_sdk = CliCommandType(
operations_tmpl="azext_aks_preview.vendored_sdks.azure_mgmt_preview_aks."
"operations._operations#MaintenanceWindowsOperations.{}",
client_factory=cf_maintenance_windows,
)

nodepool_snapshot_sdk = CliCommandType(
operations_tmpl="azext_aks_preview.vendored_sdks.azure_mgmt_preview_aks."
"operations._operations#SnapshotsOperations.{}",
Expand Down Expand Up @@ -226,6 +233,22 @@ def load_command_table(self, _):
g.custom_command("update", "aks_maintenanceconfiguration_update")
g.custom_command("delete", "aks_maintenanceconfiguration_delete")

# AKS maintenance window commands (peer ARM resource, preview API only).
# Requires the Microsoft.ContainerService/AKSSharedMaintenanceWindowPreview
# feature to be registered on the subscription; the RP enforces this and
# surfaces 403 if it isn't.
with self.command_group(
"aks maintenancewindow",
maintenance_window_sdk,
client_factory=cf_maintenance_windows,
) as g:
g.custom_command("list", "aks_maintenancewindow_list")
g.custom_show_command("show", "aks_maintenancewindow_show")
g.custom_command("create", "aks_maintenancewindow_create", supports_no_wait=True)
g.custom_command("update", "aks_maintenancewindow_update", supports_no_wait=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Double confirmation on delete

The command is registered with confirmation=True and the handler manually calls prompt_y_n. Users will be prompted twice. We should pick one way to confirm

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1. confirmation=True in commands.py registration gives users a uniform --yes flag handled by the CLI core (which honors AZURE_CORE_DISABLE_CONFIRM_PROMPT and --yes consistently with every other AKS delete), while the handler-side prompt_y_n is hand-rolled. Drop the handler-side prompt and rely on confirmation=True — the yes parameter on the handler can also go away then.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in fc075fb. Kept confirmation=True on the command registration (auto-injects --yes/-y via CLI core, respects AZURE_CORE_DISABLE_CONFIRM_PROMPT), dropped the handler-side prompt_y_n and the yes kwarg. Matches aks delete / aks nodepool delete. Verified with az aks maintenancewindow delete --help — single --yes -y arg now.

g.custom_command("delete", "aks_maintenancewindow_delete", supports_no_wait=True, confirmation=True)
g.wait_command("wait")

# AKS loadbalancer commands
with self.command_group(
"aks loadbalancer",
Expand Down
Loading
Loading