Skip to content

Commit 5e91a87

Browse files
author
Jenny Liu
committed
Fix safeguards create command AAZ context access and add missing CLIError import
- Changed self.ctx.cmd.cli_ctx to self.ctx.cli_ctx in aks_safeguards_custom.py - Added CLIError import from azure.cli.core.azclierror - Updated test recordings for all three safeguards tests - All tests passing: basic, argument_validation, with_pss
1 parent 35560d2 commit 5e91a87

File tree

7 files changed

+491
-5353
lines changed

7 files changed

+491
-5353
lines changed

src/aks-preview/azext_aks_preview/aaz/latest/aks/safeguards/_create.py

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ class Create(AAZCommand):
1818
"""Enable Deployment Safeguards for a Managed Cluster
1919
2020
:example: Create a DeploymentSafeguards resource at Warn level with a managed cluster resource id
21-
az aks safeguards create --resource /subscriptions/subid1/resourceGroups/rg1/providers/Microsoft.ContainerService/managedClusters/cluster1 --level Warn
21+
az aks safeguards create -c /subscriptions/subid1/resourceGroups/rg1/providers/Microsoft.ContainerService/managedClusters/cluster1 --level Warn
2222
2323
:example: Create a DeploymentSafeguards resource at Warn level using subscription, resourcegroup, and name tags
2424
az aks safeguards create --subscription subid1 -g rg1 -n cluster1 --level Warn
2525
2626
:example: Create a DeploymentSafeguards resource at Warn level with ignored namespaces
2727
az aks safeguards create -g rg1 -n mc1 --excluded-ns ns1 ns2 --level Warn
2828
29-
:example: Create a DeploymentSafeguards resource at Warn level with pod security standards level set to Baseline
30-
az aks safeguards create --managed-cluster subscriptions/subid1/resourceGroups/rg1/providers/Microsoft.ContainerService/managedClusters/cluster1 --level Warn --pss-level Baseline
29+
:example: Create a DeploymentSafeguards resource at Warn level with Pod Security Standards level set to Baseline
30+
az aks safeguards create --managed-cluster /subscriptions/subid1/resourceGroups/rg1/providers/Microsoft.ContainerService/managedClusters/cluster1 --level Warn --pss-level Baseline
31+
32+
:example: Create a DeploymentSafeguards resource with PSS level set to Restricted using -g/-n pattern
33+
az aks safeguards create -g rg1 -n cluster1 --level Enforce --pss-level Restricted
3134
"""
3235

3336
_aaz_info = {
@@ -56,8 +59,9 @@ def _build_arguments_schema(cls, *args, **kwargs):
5659
_args_schema = cls._args_schema
5760
_args_schema.managed_cluster = AAZStrArg(
5861
options=["-c", "--cluster", "--managed-cluster"],
59-
help="The fully qualified Azure Resource manager identifier of the Managed Cluster.",
60-
required=False, # Will be validated in custom class
62+
arg_group="",
63+
help="The name or ID of the managed cluster.",
64+
required=False, # Either this or -g/-n is required, validated in _execute_operations
6165
)
6266

6367
# define Arg Group "Properties"
@@ -77,8 +81,8 @@ def _build_arguments_schema(cls, *args, **kwargs):
7781
_args_schema.pss_level = AAZStrArg(
7882
options=["--pss-level"],
7983
arg_group="Properties",
80-
help="The pod security standards level",
81-
is_preview=True,
84+
help="The pod security standards level. Possible values are Baseline, Privileged, and Restricted",
85+
nullable=True,
8286
enum={"Baseline": "Baseline", "Privileged": "Privileged", "Restricted": "Restricted"},
8387
)
8488

@@ -87,24 +91,35 @@ def _build_arguments_schema(cls, *args, **kwargs):
8791
return cls._args_schema
8892

8993
def _execute_operations(self):
90-
# Check if Deployment Safeguards already exists BEFORE attempting create
94+
# Call pre_operations first to allow custom class to set managed_cluster
95+
self.pre_operations()
96+
97+
# Check if Deployment Safeguards already exists before attempting create
9198
from azure.cli.core.util import send_raw_request
99+
from azure.cli.core.azclierror import HTTPError
92100
from knack.util import CLIError
93101

94102
# Get the resource URI - check if managed_cluster is set, otherwise build from -g/-n
95-
resource_uri = self.ctx.args.managed_cluster
103+
resource_uri = None
96104

97-
# If managed_cluster is "Undefined" or not set, build from resource_group and cluster_name
98-
if not resource_uri or str(resource_uri) == "Undefined":
99-
# Access raw data which has resource_group and cluster_name from -g/-n
100-
data = self.ctx.args._data
101-
if 'resource_group' in data and 'cluster_name' in data:
105+
# If managed_cluster is not set, build from resource_group and cluster_name
106+
if has_value(self.ctx.args.managed_cluster):
107+
resource_uri = self.ctx.args.managed_cluster.to_serialized_data()
108+
else:
109+
# Access resource_group and cluster_name from arguments
110+
resource_group = getattr(self.ctx.args, "resource_group", None)
111+
cluster_name = getattr(self.ctx.args, "cluster_name", None)
112+
if resource_group and cluster_name:
102113
subscription = self.ctx.subscription_id
103-
resource_uri = f"/subscriptions/{subscription}/resourceGroups/{data['resource_group']}/providers/Microsoft.ContainerService/managedClusters/{data['cluster_name']}"
114+
resource_uri = f"/subscriptions/{subscription}/resourceGroups/{resource_group}/providers/Microsoft.ContainerService/managedClusters/{cluster_name}"
104115

105-
if not resource_uri or str(resource_uri) == "Undefined":
116+
if not resource_uri:
106117
raise CLIError("Resource URI not found. Please provide either --managed-cluster or both --resource-group and --name.")
107118

119+
# Validate resource_uri format to prevent URL injection
120+
if not resource_uri.startswith('/subscriptions/'):
121+
raise CLIError(f"Invalid managed cluster resource ID format: {resource_uri}")
122+
108123
# Construct the GET URL to check if resource already exists
109124
safeguards_url = f"https://management.azure.com{resource_uri}/providers/Microsoft.ContainerService/deploymentSafeguards/default?api-version=2025-05-02-preview"
110125

@@ -114,12 +129,11 @@ def _execute_operations(self):
114129
response = send_raw_request(self.ctx.cli_ctx, "GET", safeguards_url)
115130
if response.status_code == 200:
116131
resource_exists = True
117-
except Exception as ex:
118-
# Any exception (404, etc) means resource doesn't exist - that's fine for create
119-
error_str = str(ex).lower()
120-
if "404" not in error_str and "not found" not in error_str and "resourcenotfound" not in error_str:
121-
# If it's not a "not found" error, it might be a real problem - but let the create operation handle it
122-
pass
132+
except HTTPError as ex:
133+
# 404 means resource doesn't exist, which is expected for create
134+
if ex.response.status_code != 404:
135+
# Re-raise if it's not a 404 - could be auth issue, network problem, etc.
136+
raise
123137

124138
# If resource exists, block the create
125139
if resource_exists:
@@ -130,7 +144,6 @@ def _execute_operations(self):
130144
)
131145

132146
# If we get here, resource doesn't exist - proceed with create
133-
self.pre_operations()
134147
yield self.DeploymentSafeguardsCreate(ctx=self.ctx)()
135148
self.post_operations()
136149

@@ -226,9 +239,9 @@ def content(self):
226239
_content_value, _builder = self.new_content_builder(
227240
self.ctx.args,
228241
typ=AAZObjectType,
229-
typ_kwargs={"flags": {"required": True, "client_flatten": True}}
242+
typ_kwargs={"flags": {"required": True}}
230243
)
231-
_builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}})
244+
_builder.set_prop("properties", AAZObjectType)
232245

233246
properties = _builder.get(".properties")
234247
if properties is not None:
@@ -270,9 +283,7 @@ def _build_schema_on_200_201(cls):
270283
_schema_on_200_201.name = AAZStrType(
271284
flags={"read_only": True},
272285
)
273-
_schema_on_200_201.properties = AAZObjectType(
274-
flags={"client_flatten": True},
275-
)
286+
_schema_on_200_201.properties = AAZObjectType()
276287
_schema_on_200_201.system_data = AAZObjectType(
277288
serialized_name="systemData",
278289
flags={"read_only": True},

src/aks-preview/azext_aks_preview/aaz/latest/aks/safeguards/_show.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,7 @@ def _build_schema_on_200(cls):
155155
_schema_on_200.name = AAZStrType(
156156
flags={"read_only": True},
157157
)
158-
_schema_on_200.properties = AAZObjectType(
159-
flags={"client_flatten": True},
160-
)
158+
_schema_on_200.properties = AAZObjectType()
161159
_schema_on_200.system_data = AAZObjectType(
162160
serialized_name="systemData",
163161
flags={"read_only": True},

src/aks-preview/azext_aks_preview/aaz/latest/aks/safeguards/_update.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def _update_instance(self, instance):
310310
value=instance,
311311
typ=AAZObjectType
312312
)
313-
_builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}})
313+
_builder.set_prop("properties", AAZObjectType)
314314

315315
properties = _builder.get(".properties")
316316
if properties is not None:
@@ -362,9 +362,7 @@ def _build_schema_deployment_safeguard_read(cls, _schema):
362362
deployment_safeguard_read.name = AAZStrType(
363363
flags={"read_only": True},
364364
)
365-
deployment_safeguard_read.properties = AAZObjectType(
366-
flags={"client_flatten": True},
367-
)
365+
deployment_safeguard_read.properties = AAZObjectType()
368366
deployment_safeguard_read.system_data = AAZObjectType(
369367
serialized_name="systemData",
370368
flags={"read_only": True},

src/aks-preview/azext_aks_preview/aks_safeguards_custom.py

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"""
99

1010
from azure.cli.core.aaz import AAZResourceGroupNameArg, AAZStrArg, has_value
11-
from azure.cli.core.azclierror import ArgumentUsageError
11+
from azure.cli.core.azclierror import ArgumentUsageError, CLIError
12+
from azure.mgmt.core.tools import is_valid_resource_id
1213

1314
from azext_aks_preview.aaz.latest.aks.safeguards._create import Create
1415
from azext_aks_preview.aaz.latest.aks.safeguards._delete import Delete
@@ -23,113 +24,159 @@ def _validate_and_set_managed_cluster_argument(ctx):
2324
Validates that either managed_cluster OR (resource_group AND cluster_name) are provided,
2425
but not both. Then constructs the managed_cluster resource ID from -g and -n if needed.
2526
"""
27+
from azure.mgmt.core.tools import is_valid_resource_id
28+
2629
args = ctx.args
2730
has_managed_cluster = has_value(args.managed_cluster)
28-
has_rg_and_cluster = has_value(args.resource_group) and has_value(args.cluster_name)
31+
has_rg_and_cluster = has_value(
32+
args.resource_group) and has_value(args.cluster_name)
2933

3034
# Ensure exactly one of the two conditions is true
3135
if has_managed_cluster == has_rg_and_cluster:
3236
raise ArgumentUsageError(
33-
"You must provide either 'managed_cluster' or both 'resource_group' and 'cluster_name', but not both."
34-
)
37+
"You must provide either 'managed_cluster' or both 'resource_group' and 'cluster_name', but not both.")
3538

3639
if not has_managed_cluster:
37-
# Construct the managed cluster resource ID from resource group and cluster name
38-
args.managed_cluster = (
39-
f"/subscriptions/{ctx.subscription_id}/resourceGroups/{args.resource_group}/"
40-
f"providers/Microsoft.ContainerService/managedClusters/{args.cluster_name}"
41-
)
40+
# pylint: disable=line-too-long
41+
args.managed_cluster = f"/subscriptions/{ctx.subscription_id}/resourceGroups/{args.resource_group}/providers/Microsoft.ContainerService/managedClusters/{args.cluster_name}"
42+
else:
43+
# If managed_cluster is provided but is not a full resource ID, treat it as a cluster name
44+
# and require resource_group to be provided
45+
managed_cluster_value = args.managed_cluster.to_serialized_data()
46+
47+
# Normalize resource ID: add leading slash if missing for backward compatibility
48+
if managed_cluster_value and not managed_cluster_value.startswith('/'):
49+
managed_cluster_value = f"/{managed_cluster_value}"
50+
51+
if not is_valid_resource_id(managed_cluster_value):
52+
# It's just a cluster name, need resource group
53+
if not has_value(args.resource_group):
54+
raise ArgumentUsageError(
55+
"When providing cluster name via -c/--cluster, you must also provide -g/--resource-group.")
56+
# Build the full resource ID
57+
managed_cluster_value = f"/subscriptions/{ctx.subscription_id}/resourceGroups/{args.resource_group}/providers/Microsoft.ContainerService/managedClusters/{managed_cluster_value.lstrip('/')}"
4258

59+
args.managed_cluster = managed_cluster_value
4360

44-
def _add_resource_group_cluster_name_args(_args_schema):
61+
62+
def _add_resource_group_cluster_name_subscription_id_args(_args_schema):
4563
"""
4664
Adds -g/--resource-group and -n/--name arguments to the schema and makes
4765
managed_cluster optional (so users can choose either pattern).
4866
"""
4967
_args_schema.resource_group = AAZResourceGroupNameArg(
5068
options=["-g", "--resource-group"],
51-
help=r"The name of the resource group. You can configure the default group using "
52-
r"`az configure --defaults group=`<name>``. You may provide either --managed-cluster "
53-
r"or both --resource-group and --name, but not both.",
69+
# pylint: disable=line-too-long
70+
help="The name of the resource group. You can configure the default group using az configure --defaults group=`<name>`. You may provide either 'managed_cluster' or both 'resource_group' and 'name', but not both",
5471
required=False,
5572
)
5673
_args_schema.cluster_name = AAZStrArg(
5774
options=["--name", "-n"],
58-
help="The name of the Managed Cluster. You may provide either --managed-cluster "
59-
"or both --resource-group and --name, but not both.",
75+
# pylint: disable=line-too-long
76+
help="The name of the Managed Cluster.You may provide either 'managed_cluster' or both 'resource_group' and name', but not both.",
6077
required=False,
6178
)
6279
_args_schema.managed_cluster.required = False
6380
return _args_schema
6481

6582

6683
class AKSSafeguardsShowCustom(Show):
67-
"""Custom Show command for AKS Safeguards with -g/-n support"""
6884

6985
def pre_operations(self):
7086
_validate_and_set_managed_cluster_argument(self.ctx)
7187

7288
@classmethod
7389
def _build_arguments_schema(cls, *args, **kwargs):
7490
_args_schema = super()._build_arguments_schema(*args, **kwargs)
75-
return _add_resource_group_cluster_name_args(_args_schema)
91+
after_schema = _add_resource_group_cluster_name_subscription_id_args(
92+
_args_schema)
93+
return after_schema
7694

7795

7896
class AKSSafeguardsDeleteCustom(Delete):
79-
"""Custom Delete command for AKS Safeguards with -g/-n support"""
8097

8198
def pre_operations(self):
8299
_validate_and_set_managed_cluster_argument(self.ctx)
83100

84101
@classmethod
85102
def _build_arguments_schema(cls, *args, **kwargs):
86103
_args_schema = super()._build_arguments_schema(*args, **kwargs)
87-
return _add_resource_group_cluster_name_args(_args_schema)
104+
return _add_resource_group_cluster_name_subscription_id_args(_args_schema)
88105

89106

90107
class AKSSafeguardsUpdateCustom(Update):
91-
"""Custom Update command for AKS Safeguards with -g/-n support"""
92108

93109
def pre_operations(self):
94110
_validate_and_set_managed_cluster_argument(self.ctx)
95111

96112
@classmethod
97113
def _build_arguments_schema(cls, *args, **kwargs):
98114
_args_schema = super()._build_arguments_schema(*args, **kwargs)
99-
return _add_resource_group_cluster_name_args(_args_schema)
115+
return _add_resource_group_cluster_name_subscription_id_args(_args_schema)
100116

101117

102118
class AKSSafeguardsCreateCustom(Create):
103-
"""Custom Create command for AKS Safeguards with -g/-n support"""
104-
105-
def pre_operations(self):
106-
_validate_and_set_managed_cluster_argument(self.ctx)
107119

108120
@classmethod
109121
def _build_arguments_schema(cls, *args, **kwargs):
110122
_args_schema = super()._build_arguments_schema(*args, **kwargs)
111-
return _add_resource_group_cluster_name_args(_args_schema)
123+
return _add_resource_group_cluster_name_subscription_id_args(_args_schema)
124+
125+
def pre_operations(self):
126+
from azure.cli.core.util import send_raw_request
127+
from azure.cli.core.azclierror import HTTPError
128+
129+
# Validate and set managed cluster argument
130+
_validate_and_set_managed_cluster_argument(self.ctx)
131+
132+
# Check if Deployment Safeguards already exists before attempting create
133+
resource_uri = self.ctx.args.managed_cluster.to_serialized_data()
134+
135+
# Validate resource_uri format to prevent URL injection
136+
if not resource_uri.startswith('/subscriptions/'):
137+
raise CLIError(f"Invalid managed cluster resource ID format: {resource_uri}")
138+
139+
# Construct the GET URL to check if resource already exists
140+
safeguards_url = f"https://management.azure.com{resource_uri}/providers/Microsoft.ContainerService/deploymentSafeguards/default?api-version=2025-05-02-preview"
141+
142+
# Check if resource already exists
143+
resource_exists = False
144+
try:
145+
response = send_raw_request(self.ctx.cli_ctx, "GET", safeguards_url)
146+
if response.status_code == 200:
147+
resource_exists = True
148+
except HTTPError as ex:
149+
# 404 means resource doesn't exist, which is expected for create
150+
if ex.response.status_code != 404:
151+
# Re-raise if it's not a 404 - could be auth issue, network problem, etc.
152+
raise
153+
154+
# If resource exists, block the create
155+
if resource_exists:
156+
raise CLIError(
157+
"Deployment Safeguards instance already exists for this cluster. "
158+
"Please use 'az aks safeguards update' to modify the configuration, "
159+
"or 'az aks safeguards delete' to remove it before creating a new one."
160+
)
112161

113162

114163
class AKSSafeguardsListCustom(List):
115-
"""Custom List command for AKS Safeguards with -g/-n support"""
116164

117165
def pre_operations(self):
118166
_validate_and_set_managed_cluster_argument(self.ctx)
119167

120168
@classmethod
121169
def _build_arguments_schema(cls, *args, **kwargs):
122170
_args_schema = super()._build_arguments_schema(*args, **kwargs)
123-
return _add_resource_group_cluster_name_args(_args_schema)
171+
return _add_resource_group_cluster_name_subscription_id_args(_args_schema)
124172

125173

126174
class AKSSafeguardsWaitCustom(Wait):
127-
"""Custom Wait command for AKS Safeguards with -g/-n support"""
128175

129176
def pre_operations(self):
130177
_validate_and_set_managed_cluster_argument(self.ctx)
131178

132179
@classmethod
133180
def _build_arguments_schema(cls, *args, **kwargs):
134181
_args_schema = super()._build_arguments_schema(*args, **kwargs)
135-
return _add_resource_group_cluster_name_args(_args_schema)
182+
return _add_resource_group_cluster_name_subscription_id_args(_args_schema)

0 commit comments

Comments
 (0)