Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 5 additions & 1 deletion src/fleet/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,8 @@ Release History

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.
* Add 2026-02-01-preview API Version with UpdateRun MaxConcurrency support. Add fix for ControlPlaneOnly upgrade type requiring no node image selection.

2.0.0
Comment thread
frantran marked this conversation as resolved.
Outdated
++++++
* Add 2026-03-02-preview API version with ClusterMesh support.
4 changes: 2 additions & 2 deletions src/fleet/azext_fleet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# --------------------------------------------------------------------------------------------

from azure.cli.core import AzCommandsLoader
from azure.cli.core.profiles import register_resource_type, SDKProfile
from azure.cli.core.profiles import register_resource_type

# pylint: disable=unused-import
from azext_fleet._help import helps
Expand All @@ -15,7 +15,7 @@ def register_fleet_resource_type():
register_resource_type(
"latest",
CUSTOM_MGMT_FLEET,
SDKProfile("2026-02-01-preview"),
None,
)


Expand Down
14 changes: 13 additions & 1 deletion src/fleet/azext_fleet/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@
CUSTOM_MGMT_FLEET = CustomResourceType('azext_fleet.vendored_sdks', 'ContainerServiceFleetMgmtClient')


FLEET_BASE_URL = "https://management.azure.com"


# container service clients
def get_container_service_client(cli_ctx, subscription_id=None):
return get_mgmt_service_client(cli_ctx, CUSTOM_MGMT_FLEET, subscription_id=subscription_id)
return get_mgmt_service_client(
cli_ctx, CUSTOM_MGMT_FLEET,
subscription_id=subscription_id,
base_url_bound=False,
base_url=FLEET_BASE_URL
)
Comment thread
frantran marked this conversation as resolved.
Outdated


def cf_fleets(cli_ctx, *_):
Expand Down Expand Up @@ -56,6 +64,10 @@ def cf_auto_upgrade_profile_operations(cli_ctx, *_):
return get_container_service_client(cli_ctx).auto_upgrade_profile_operations


def cf_cluster_mesh_profiles(cli_ctx, *_):
return get_container_service_client(cli_ctx).cluster_mesh_profiles


def get_provider_client(cli_ctx):
return get_mgmt_service_client(
cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
Expand Down
68 changes: 68 additions & 0 deletions src/fleet/azext_fleet/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@
examples:
- name: List all members for a given fleet.
text: az fleet member list -g MyFleetResourceGroup -f MyFleetName
- name: List members filtered by a cluster mesh profile.
text: az fleet member list -g MyFleetResourceGroup -f MyFleetName --cluster-mesh-profile MyClusterMeshProfile
"""

helps['fleet member show'] = """
Expand Down Expand Up @@ -584,3 +586,69 @@
- name: Save kubeconfig to a specific file.
text: az fleet namespace get-credentials -g MyFleetResourceGroup -f MyFleetName -n MyManagedNamespace --file ~/my-namespace-config
"""

helps['fleet clustermeshprofile'] = """
type: group
short-summary: Commands to manage cluster mesh profiles.
"""

helps['fleet clustermeshprofile create'] = """
type: command
short-summary: Creates or updates a cluster mesh profile.
Comment thread
frantran marked this conversation as resolved.
Outdated
parameters:
- name: --member-selector --selector -s
type: string
short-summary: "Kubernetes-style label selector for selecting Fleet members, e.g. 'env=production'."
examples:
- name: Create a cluster mesh profile with a label selector.
text: az fleet clustermeshprofile create -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile --selector "env=production"
- name: Create a cluster mesh profile without a selector (no members selected initially).
text: az fleet clustermeshprofile create -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile
"""

helps['fleet clustermeshprofile show'] = """
type: command
short-summary: Gets a cluster mesh profile.
examples:
- name: Show the details of a cluster mesh profile.
text: az fleet clustermeshprofile show -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile
"""

helps['fleet clustermeshprofile list'] = """
type: command
short-summary: Lists all cluster mesh profiles for a fleet.
examples:
- name: List all cluster mesh profiles for a given fleet.
text: az fleet clustermeshprofile list -g MyFleetResourceGroup -f MyFleetName
"""

helps['fleet clustermeshprofile delete'] = """
type: command
short-summary: Deletes a cluster mesh profile. All members must be removed from the cluster mesh profile before it can be deleted.
examples:
- name: Delete a specific cluster mesh profile.
text: az fleet clustermeshprofile delete -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile
"""

helps['fleet clustermeshprofile apply'] = """
type: command
short-summary: Applies the cluster mesh profile to selected fleet members.
examples:
- name: Apply a cluster mesh profile.
text: az fleet clustermeshprofile apply -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile
- name: Preview what changes would be made without actually applying.
text: az fleet clustermeshprofile apply -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile --what-if --output table
"""

helps['fleet clustermeshprofile list-members'] = """
type: command
short-summary: Lists fleet members for a cluster mesh profile.
long-summary: |
Without --selector, lists members currently applied to the mesh profile.
With --selector, lists members that would match the profile's label selector (i.e. candidates for the next apply).
examples:
- name: List members currently applied to the mesh.
text: az fleet clustermeshprofile list-members -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile
- name: List members that would match the profile's selector.
text: az fleet clustermeshprofile list-members -g MyFleetResourceGroup -f MyFleetName -n MyClusterMeshProfile --selector
"""
21 changes: 21 additions & 0 deletions src/fleet/azext_fleet/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ def load_arguments(self, _):
help='Space-separated labels in key=value format. Example: env=production region=us-west team=devops'
)

with self.argument_context('fleet member list') as c:
c.argument('cluster_mesh_profile', options_list=['--cluster-mesh-profile'],
help='Filter members by cluster mesh profile name.')

with self.argument_context('fleet updaterun') as c:
c.argument('name', options_list=['--name', '-n'], help='Specify name for the update run.')
c.argument('fleet_name', options_list=['--fleet-name', '-f'], help='Specify the fleet name.')
Expand Down Expand Up @@ -207,3 +211,20 @@ def load_arguments(self, _):
c.argument('overwrite_existing', help='Overwrite any existing cluster entry with the same name.')
c.argument('path', options_list=['--file'], type=file_type, completer=FilesCompleter(),
default=os.path.join(os.path.expanduser('~'), '.kube', 'config'))

with self.argument_context('fleet clustermeshprofile') as c:
c.argument('name', options_list=['--name', '-n'], help='Specify name for the cluster mesh profile.')
c.argument('fleet_name', options_list=['--fleet-name', '-f'], help='Specify the fleet name.')

with self.argument_context('fleet clustermeshprofile create') as c:
c.argument('member_selector', options_list=['--member-selector', '--selector', '-s'],
help='Kubernetes-style label selector for selecting Fleet members, e.g. "env=production".')

with self.argument_context('fleet clustermeshprofile apply') as c:
c.argument('what_if', action='store_true', options_list=['--what-if'],
help='Show what changes would be made by the apply operation without actually performing it.')

with self.argument_context('fleet clustermeshprofile list-members') as c:
c.argument('selector', action='store_true', options_list=['--selector'],
help='Filter by the profile\'s label selector (members that would match after apply) '
'instead of currently applied members.')
18 changes: 17 additions & 1 deletion src/fleet/azext_fleet/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
cf_auto_upgrade_profiles,
cf_auto_upgrade_profile_operations,
cf_gates,
cf_fleet_managed_namespaces
cf_fleet_managed_namespaces,
cf_cluster_mesh_profiles
)


Expand Down Expand Up @@ -67,6 +68,12 @@ def load_command_table(self, _):
client_factory=cf_gates
)

cluster_mesh_profiles_sdk = CliCommandType(
operations_tmpl="azext_fleet.vendored_sdks.operations._cluster_mesh_profiles_operations#ClusterMeshProfilesOperations.{}",
operation_group="cluster_mesh_profiles",
client_factory=cf_cluster_mesh_profiles
)

# fleets command group
with self.command_group("fleet", fleets_sdk, client_factory=cf_fleets) as g:
g.custom_command("create", "create_fleet", supports_no_wait=True)
Expand Down Expand Up @@ -135,3 +142,12 @@ def load_command_table(self, _):
g.custom_show_command("show", "show_managed_namespace")
g.custom_command("get-credentials", "get_namespace_credentials")
g.wait_command("wait")

# cluster mesh profiles command group
with self.command_group("fleet clustermeshprofile", cluster_mesh_profiles_sdk, client_factory=cf_cluster_mesh_profiles, is_preview=True) as g:
g.custom_command("create", "create_cluster_mesh_profile", supports_no_wait=True)
g.custom_show_command("show", "show_cluster_mesh_profile")
g.custom_command("list", "list_cluster_mesh_profiles")
g.custom_command("delete", "delete_cluster_mesh_profile", supports_no_wait=True, confirmation=True)
g.custom_command("apply", "apply_cluster_mesh_profile", supports_no_wait=True)
g.custom_command("list-members", "list_cluster_mesh_profile_members")
162 changes: 158 additions & 4 deletions src/fleet/azext_fleet/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@
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.v2026_02_01_preview.models import (
from azext_fleet.vendored_sdks.v2026_03_02_preview.models import (
PropagationPolicy,
PlacementProfile,
PlacementV1ClusterResourcePlacementSpec,
PlacementV1PlacementPolicy,
PropagationType,
PlacementType
PlacementType,
)

logger = get_logger(__name__)
Expand Down Expand Up @@ -381,8 +381,12 @@ def update_fleet_member(cmd,
def list_fleet_member(cmd, # pylint: disable=unused-argument
client,
resource_group_name,
fleet_name):
return client.list_by_fleet(resource_group_name, fleet_name)
fleet_name,
cluster_mesh_profile=None):
filter_expr = None
if cluster_mesh_profile:
filter_expr = f"clusterMeshProfile eq {cluster_mesh_profile}"
Comment thread
frantran marked this conversation as resolved.
return client.list_by_fleet(resource_group_name, fleet_name, filter=filter_expr)


def show_fleet_member(cmd, # pylint: disable=unused-argument
Expand Down Expand Up @@ -1066,3 +1070,153 @@ def get_namespace_credentials(cmd,

# Apply kubelogin conversion to the final file after namespace modification
_convert_kubeconfig_to_azurecli(path)


def create_cluster_mesh_profile(cmd,
client,
resource_group_name,
fleet_name,
name,
member_selector=None,
no_wait=False):
cluster_mesh_profile_model = cmd.get_models(
"ClusterMeshProfile",
resource_type=CUSTOM_MGMT_FLEET,
operation_group="cluster_mesh_profiles"
)
cluster_mesh_profile_properties_model = cmd.get_models(
"ClusterMeshProfileProperties",
resource_type=CUSTOM_MGMT_FLEET,
operation_group="cluster_mesh_profiles"
)
member_selector_model = cmd.get_models(
"MemberSelector",
resource_type=CUSTOM_MGMT_FLEET,
operation_group="cluster_mesh_profiles"
)

selector = None
if member_selector is not None:
selector = member_selector_model(by_label=member_selector)

properties = cluster_mesh_profile_properties_model(member_selector=selector)
profile = cluster_mesh_profile_model(properties=properties)

return sdk_no_wait(
no_wait,
client.begin_create_or_update,
resource_group_name,
fleet_name,
name,
profile
)


def show_cluster_mesh_profile(cmd, # pylint: disable=unused-argument
client,
resource_group_name,
fleet_name,
name):
return client.get(resource_group_name, fleet_name, name)


def list_cluster_mesh_profiles(cmd, # pylint: disable=unused-argument
client,
resource_group_name,
fleet_name):
return client.list_by_fleet(resource_group_name, fleet_name)


def delete_cluster_mesh_profile(cmd, # pylint: disable=unused-argument
client,
resource_group_name,
fleet_name,
name,
no_wait=False):
return sdk_no_wait(no_wait, client.begin_delete, resource_group_name, fleet_name, name)


def apply_cluster_mesh_profile(cmd,
client,
resource_group_name,
fleet_name,
name,
what_if=False,
no_wait=False):
if what_if:
return _apply_cluster_mesh_what_if(cmd, resource_group_name, fleet_name, name)

return sdk_no_wait(no_wait, client.begin_apply, resource_group_name, fleet_name, name)

Comment thread
frantran marked this conversation as resolved.

def list_cluster_mesh_profile_members(cmd,
client, # pylint: disable=unused-argument
resource_group_name,
fleet_name,
name,
selector=False):
"""List fleet members for a cluster mesh profile.

Modes:
--name cmp-1 members currently applied to the mesh
(server-side: $filter=clusterMeshProfile eq cmp-1)
--name cmp-1 --selector members matching the profile's label selector
(server-side: $filter=clusterMeshProfile.Selector eq cmp-1)
"""
members_client = cf_fleet_members(cmd.cli_ctx)
if selector:
filter_expr = f"clusterMeshProfile.Selector eq {name}"
else:
filter_expr = f"clusterMeshProfile eq {name}"
return members_client.list_by_fleet(resource_group_name, fleet_name, filter=filter_expr)
Comment thread
frantran marked this conversation as resolved.


def _apply_cluster_mesh_what_if(cmd, resource_group_name, fleet_name, name):
"""Simulate apply by comparing currently-applied members vs selector-matched members."""
members_client = cf_fleet_members(cmd.cli_ctx)

# Members currently in the mesh (already applied)
current_filter = f"clusterMeshProfile eq {name}"
current_members = {
m.name: m for m in members_client.list_by_fleet(
resource_group_name, fleet_name, filter=current_filter
)
}

# Members that match the selector (would be in the mesh after apply)
selector_filter = f"clusterMeshProfile.Selector eq {name}"
desired_members = {
m.name: m for m in members_client.list_by_fleet(
resource_group_name, fleet_name, filter=selector_filter
)
Comment thread
frantran marked this conversation as resolved.
Outdated
}

results = []
all_names = set(current_members.keys()) | set(desired_members.keys())

for member_name in sorted(all_names):
in_current = member_name in current_members
in_desired = member_name in desired_members

member = desired_members.get(member_name) or current_members.get(member_name)

mesh_state = None
if member.mesh_properties and member.mesh_properties.status:
mesh_state = member.mesh_properties.status.state

if in_desired and not in_current:
action = "Add"
elif in_current and not in_desired:
action = "Remove"
else:
action = "-"

results.append({
"ClusterResourceId": member.cluster_resource_id,
"ETag": member.e_tag,
"Name": member.name,
"Action": action,
"MeshMembershipState": mesh_state or "-"
})

return results
Loading
Loading