Skip to content

Commit 5c05310

Browse files
authored
Merge pull request Azure#454 from necusjz/identity-subcommand-patch
support get + patch on identity subcommand
2 parents 62df0c7 + cddeb59 commit 5c05310

File tree

5 files changed

+197
-30
lines changed

5 files changed

+197
-30
lines changed

src/aaz_dev/cli/templates/aaz/command/_cmd.py.j2

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,18 @@ class {{ leaf.cls_name }}(
489489

490490
@property
491491
def content(self):
492+
{%- if op.content.ref is not none and op.content._json.schema is not none -%}
493+
494+
{%- if op.method == "PATCH" %}
495+
{%- set valid_ops = leaf.operations | selectattr("is_selector_variant") | list %}
496+
{%- if valid_ops %}
497+
{%- set temp_op = valid_ops[0] %}
498+
identity = self.serialize_content({{ temp_op.variant_key }}.required())
499+
return {"identity": identity}
500+
{%- endif %}
501+
{%- endif %}
502+
503+
{%- else %}
492504
{{ op.content.VALUE_NAME}}, {{ op.content.BUILDER_NAME }} = self.new_content_builder(
493505
{{ op.content.arg_key }},
494506
{%- if op.content.ref is not none %}
@@ -546,6 +558,7 @@ class {{ leaf.cls_name }}(
546558
{%- endif %}
547559

548560
return self.serialize_content({{ op.content.VALUE_NAME }})
561+
{%- endif %}
549562
{%- endif %}
550563

551564
{%- if op.form_content is not none %}

src/aaz_dev/command/controller/cfg_reader.py

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,74 @@ def find_sub_schema(cls, schema, idx):
744744

745745
return None
746746

747+
@classmethod
748+
def trim_identity_schema_by_idx(cls, schema, idx): # only keep identity and its parents
749+
if not schema:
750+
return
751+
752+
current_idx = idx[0] if idx else None
753+
remain_idx = idx[1:] if idx else None
754+
755+
identity_schema = schema.__class__(raw_data=schema.to_native())
756+
if isinstance(identity_schema, CMDObjectSchemaBase):
757+
if current_idx == '{}':
758+
if identity_schema.additional_props and identity_schema.additional_props.item:
759+
identity_schema.additional_props.item = cls.trim_identity_schema_by_idx(identity_schema.additional_props.item, remain_idx)
760+
761+
elif identity_schema.props:
762+
if current_idx:
763+
props = []
764+
for prop in identity_schema.props:
765+
if current_idx == prop.name:
766+
props.append(cls.trim_identity_schema_by_idx(prop, remain_idx))
767+
identity_schema.props = props
768+
else:
769+
identity_schema.props = None
770+
if isinstance(identity_schema, CMDIdentityObjectSchema):
771+
identity_schema.system_assigned = None
772+
identity_schema.user_assigned = None
773+
774+
elif identity_schema.discriminators:
775+
if current_idx:
776+
discriminators = []
777+
for disc in identity_schema.discriminators:
778+
if current_idx == disc.get_safe_value():
779+
discriminators.append(cls.trim_identity_schema_by_idx(disc, remain_idx))
780+
identity_schema.discriminators = discriminators
781+
else:
782+
identity_schema.discriminators = None
783+
784+
elif isinstance(identity_schema, CMDObjectSchemaDiscriminator):
785+
if identity_schema.props:
786+
if current_idx:
787+
props = []
788+
for prop in identity_schema.props:
789+
if current_idx == prop.name:
790+
props.append(cls.trim_identity_schema_by_idx(prop, remain_idx))
791+
identity_schema.props = props
792+
else:
793+
identity_schema.props = None
794+
795+
elif identity_schema.discriminators:
796+
if current_idx:
797+
discriminators = []
798+
for disc in identity_schema.discriminators:
799+
if current_idx == disc.get_safe_value():
800+
discriminators.append(cls.trim_identity_schema_by_idx(disc, remain_idx))
801+
identity_schema.discriminators = discriminators
802+
else:
803+
identity_schema.discriminators = None
804+
805+
elif isinstance(identity_schema, CMDArraySchemaBase):
806+
if current_idx == '[]':
807+
identity_schema.item = cls.trim_identity_schema_by_idx(identity_schema.item, remain_idx)
808+
809+
return identity_schema
810+
747811
@classmethod
748812
def find_identity_schema_in_command(cls, command):
749813
for operation in command.operations:
750-
if isinstance(operation, CMDInstanceUpdateOperation):
814+
if isinstance(operation, CMDInstanceUpdateOperation) or isinstance(operation, CMDHttpOperation):
751815
for match in cls.iter_schema_in_update_operation_by_identity(operation):
752816
return operation, *match
753817

@@ -759,11 +823,17 @@ def schema_filter(_parent, _schema, _schema_idx):
759823
return (_parent, _schema, _schema_idx), False
760824
return None, False
761825

762-
for parent, schema, schema_idx in cls._iter_schema_in_json(
763-
operation.instance_update.json, schema_filter=schema_filter):
764-
if schema:
765-
schema_idx = [_SchemaIdxEnum.Instance, _SchemaIdxEnum.Update, *schema_idx]
766-
yield parent, schema, schema_idx
826+
if isinstance(operation, CMDInstanceUpdateOperation):
827+
for parent, schema, schema_idx in cls._iter_schema_in_json(operation.instance_update.json, schema_filter=schema_filter):
828+
if schema:
829+
schema_idx = [_SchemaIdxEnum.Instance, _SchemaIdxEnum.Update, *schema_idx]
830+
yield parent, schema, schema_idx
831+
832+
else:
833+
for parent, schema, schema_idx in cls._iter_schema_in_request(operation.http.request, schema_filter=schema_filter):
834+
if schema:
835+
schema_idx = [_SchemaIdxEnum.Instance, _SchemaIdxEnum.Update, *schema_idx]
836+
yield parent, schema, schema_idx
767837

768838
@classmethod
769839
def iter_schema_in_command_by_arg_var(cls, command, arg_var):

src/aaz_dev/command/controller/workspace_cfg_editor.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,14 +1064,14 @@ def remove_subresource_commands(self, resource_id, version, subresource):
10641064
self.reformat()
10651065
return commands
10661066

1067-
def build_identity_subresource(self, resource_id, temp_generic_update_cmd=None):
1067+
def build_identity_subresource(self, resource_id, temp_update_cmd=None):
10681068
update_cmd_info = self.get_update_cmd(resource_id)
10691069
if not update_cmd_info:
10701070
return
10711071
update_cmd_names, update_cmd, update_by = update_cmd_info
1072-
if update_by == "PatchOnly" and temp_generic_update_cmd is not None:
1073-
# generate temp update command using generic update
1074-
update_cmd = temp_generic_update_cmd
1072+
if update_by == "PatchOnly" and temp_update_cmd is not None:
1073+
# generate temp update command using get + patch
1074+
update_cmd = temp_update_cmd
10751075
update_cmd.name = ' '.join(update_cmd_names)
10761076
elif update_by != "GenericOnly":
10771077
return
@@ -1405,7 +1405,9 @@ def _generate_sub_commands(cls, schema, subresource_idx, update_cmd, ref_args_op
14051405
def _build_sub_command_base(cls, update_cmd, subresource_idx, **kwargs):
14061406
get_op = None
14071407
put_op = None
1408+
patch_op = None
14081409
update_op = None
1410+
14091411
for operation in update_cmd.operations:
14101412
if isinstance(operation, CMDInstanceUpdateOperation):
14111413
update_op = operation
@@ -1414,8 +1416,11 @@ def _build_sub_command_base(cls, update_cmd, subresource_idx, **kwargs):
14141416
get_op = operation
14151417
if operation.http.request.method == "put":
14161418
put_op = operation
1419+
if operation.http.request.method == "patch":
1420+
patch_op = operation
1421+
14171422
assert get_op is not None
1418-
assert put_op is not None
1423+
assert put_op is not None or patch_op is not None
14191424
assert update_op is not None
14201425

14211426
# find response body
@@ -1440,7 +1445,11 @@ def _build_sub_command_base(cls, update_cmd, subresource_idx, **kwargs):
14401445
_sub_command.resources = [_resource]
14411446
_sub_command.subresource_selector = cls._build_subresource_selector(
14421447
response_json, update_json, subresource_idx, **kwargs)
1443-
return _sub_command, get_op, put_op, update_json
1448+
1449+
if patch_op is not None:
1450+
return _sub_command, get_op, patch_op, update_json
1451+
if put_op is not None:
1452+
return _sub_command, get_op, put_op, update_json
14441453

14451454
@classmethod
14461455
def _build_subresource_list_or_show_command(cls, update_cmd, subresource_idx, ref_args, ref_options):
@@ -1491,7 +1500,7 @@ def _build_subresource_create_command(cls, update_cmd, subresource_idx, ref_args
14911500

14921501
@classmethod
14931502
def _build_subresource_update_command(cls, update_cmd, subresource_idx, ref_args, ref_options, action=None):
1494-
_sub_command, get_op, put_op, update_json = cls._build_sub_command_base(update_cmd, subresource_idx)
1503+
_sub_command, get_op, update_op, update_json = cls._build_sub_command_base(update_cmd, subresource_idx)
14951504

14961505
_instance_op = CMDInstanceUpdateOperation()
14971506
_instance_op.instance_update = CMDJsonInstanceUpdateAction()
@@ -1519,10 +1528,14 @@ def _build_subresource_update_command(cls, update_cmd, subresource_idx, ref_args
15191528
_instance_op_schema.required = True
15201529
_instance_op.instance_update.json.schema = _instance_op_schema
15211530

1531+
put_or_patch = update_op.__class__(raw_data=update_op.to_native())
1532+
if update_op.http.request.method == "patch":
1533+
put_or_patch.http.request.body.json.schema = cls.trim_identity_schema_by_idx(put_or_patch.http.request.body.json.schema, subresource_idx)
1534+
15221535
_sub_command.operations = [
15231536
get_op.__class__(raw_data=get_op.to_native()),
15241537
_instance_op,
1525-
put_op.__class__(raw_data=put_op.to_native()),
1538+
put_or_patch
15261539
]
15271540
_sub_command.generate_args(ref_args=ref_args, ref_options=ref_options)
15281541
_sub_command.generate_outputs(ref_outputs=update_cmd.outputs)

src/aaz_dev/command/controller/workspace_manager.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -666,13 +666,13 @@ def _add_new_resources(self, command_generator, resources, resource_options):
666666
aaz_ref[' '.join(cmd_names)] = aaz_version
667667

668668
update_cmd_info = cfg_editor.get_update_cmd(resource.id)
669-
temp_generic_update_cmd = None
670-
if update_cmd_info and options.get('update_by', None) == "PatchOnly":
671-
temp_cfg_editor = self._build_draft_cfg_editor(command_generator, resource, {"update_by": "GenericOnly", "methods": ('get', 'put')})
669+
temp_update_cmd = None
670+
if update_cmd_info and options.get('update_by', None) == "PatchOnly" and cfg_editor.find_identity_schema_in_command(update_cmd_info[1]):
671+
temp_cfg_editor = self._build_draft_cfg_editor(command_generator, resource, {"update_by": "PatchOnly", "methods": ('get', 'patch'), "is_identity": True})
672672
temp_update_cmd_info = temp_cfg_editor.get_update_cmd(resource.id)
673673
if temp_update_cmd_info:
674-
_, temp_generic_update_cmd, _ = temp_update_cmd_info
675-
cfg_editor.build_identity_subresource(resource.id, temp_generic_update_cmd)
674+
_, temp_update_cmd, _ = temp_update_cmd_info
675+
cfg_editor.build_identity_subresource(resource.id, temp_update_cmd)
676676
cfg_editors.append(cfg_editor)
677677

678678
# add cfg_editors
@@ -789,17 +789,18 @@ def _reload_resources(self, command_generator, reload_resource_map):
789789
options['methods'] = methods
790790
update_cmd_info = cfg_editor.get_update_cmd(resource_id)
791791
if update_cmd_info:
792-
_, _, update_by = update_cmd_info
792+
_, command, update_by = update_cmd_info
793793
options['update_by'] = update_by
794794
new_cfg_editor = self._build_draft_cfg_editor(command_generator, resource, options)
795795
new_cfg_editor.inherit_modification(cfg_editor)
796-
temp_generic_update_cmd = None
797-
if update_cmd_info and update_by == "PatchOnly":
798-
temp_cfg_editor = self._build_draft_cfg_editor(command_generator, resource, {"update_by": "GenericOnly", "methods": ('get', 'put')})
796+
temp_update_cmd = None
797+
if update_cmd_info and update_by == "PatchOnly" and cfg_editor.find_identity_schema_in_command(command):
798+
temp_cfg_editor = self._build_draft_cfg_editor(command_generator, resource, {"update_by": "PatchOnly", "methods": ('get', 'patch'), "is_identity": True})
799799
temp_update_cmd_info = temp_cfg_editor.get_update_cmd(resource_id)
800800
if temp_update_cmd_info:
801-
_, temp_generic_update_cmd, _ = temp_update_cmd_info
802-
new_cfg_editor.build_identity_subresource(resource_id, temp_generic_update_cmd)
801+
_, temp_update_cmd, _ = temp_update_cmd_info
802+
803+
new_cfg_editor.build_identity_subresource(resource_id, temp_update_cmd)
803804
new_cfg_editors.append(new_cfg_editor)
804805

805806
# remove old cfg editor

src/aaz_dev/swagger/controller/command_generator.py

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,52 @@ def generate_generic_update_command(cls, path_item, resource, instance_var, cmd_
102102
command.name = f"{group_name} update"
103103
return command
104104

105+
@classmethod
106+
def generate_specific_update_command(cls, path_item, resource, instance_var, cmd_builder, get_op, patch_op):
107+
command = CMDCommand()
108+
command.version = cls.generate_command_version(resource)
109+
command.resources = [
110+
resource.to_cmd() if not isinstance(resource, CMDResource) else resource
111+
]
112+
assert path_item.get is not None
113+
assert path_item.patch is not None
114+
115+
cmd_builder.apply_cls_definitions(get_op, patch_op)
116+
117+
if patch_op.http.request.body is None:
118+
return None
119+
120+
if not cls._set_api_version_parameter(get_op.http.request, api_version=resource.version):
121+
logger.warning(f"Cannot Find api version parameter: {resource.path}, 'get' : {path_item.traces}")
122+
if not cls._set_api_version_parameter(patch_op.http.request, api_version=resource.version):
123+
logger.warning(f"Cannot Find api version parameter: {resource.path}, 'patch' : {path_item.traces}")
124+
125+
if not command.build_output_by_operation(get_op):
126+
return None
127+
128+
if not command.build_output_by_operation(patch_op):
129+
return None
130+
131+
cls._filter_generic_update_parameters(get_op, patch_op)
132+
133+
command.description = patch_op.description
134+
json_update_op = cls._generate_instance_patch_operation(patch_op, instance_var)
135+
command.operations = [
136+
get_op,
137+
json_update_op,
138+
patch_op
139+
]
140+
141+
command.generate_args()
142+
command.generate_outputs()
143+
144+
assert command.outputs
145+
146+
group_name = cls.generate_command_group_name_by_resource(
147+
resource_path=resource.path, rp_name=resource.rp_name)
148+
command.name = f"{group_name} update"
149+
return command
150+
105151
@staticmethod
106152
def _set_api_version_parameter(request, api_version):
107153
assert isinstance(request, CMDHttpRequest)
@@ -254,6 +300,17 @@ def _generate_instance_update_operation(put_op, instance_var):
254300
put_op.http.request.body.json.schema = None
255301
return json_update_op
256302

303+
@staticmethod
304+
def _generate_instance_patch_operation(patch_op, instance_var):
305+
json_update_op = CMDInstanceUpdateOperation()
306+
json_update_op.instance_update = CMDJsonInstanceUpdateAction()
307+
json_update_op.instance_update.ref = instance_var
308+
json_update_op.instance_update.json = CMDRequestJson()
309+
json_update_op.instance_update.json.schema = patch_op.http.request.body.json.schema
310+
311+
patch_op.http.request.body.json.ref = instance_var
312+
return json_update_op
313+
257314
@staticmethod
258315
def _filter_generic_update_parameters(get_op, put_op):
259316
"""Get operation may contain useless query or header parameters for update, ignore them"""
@@ -470,11 +527,24 @@ def create_draft_command_group(self, resource,
470527
raise exceptions.InvalidAPIUsage(f"Invalid update_by resource: resource needs to have 'patch' operation: '{resource}'")
471528
if 'patch' not in methods:
472529
raise exceptions.InvalidAPIUsage(f"Invalid update_by resource: '{resource}': 'patch' not in methods: '{methods}'")
473-
cmd_builder = CMDBuilder(path=resource.path, method='patch', mutability=MutabilityEnum.Update,
474-
parameterized_host=parameterized_host)
475-
op = self.generate_operation(cmd_builder, path_item, instance_var)
476-
patch_update_command = self.generate_command(path_item, resource, instance_var, cmd_builder, op)
477-
command_group.commands.append(patch_update_command)
530+
531+
if kwargs.get('is_identity', False) is True:
532+
cmd_builder = CMDBuilder(path=resource.path, parameterized_host=parameterized_host)
533+
get_op = self.generate_operation(cmd_builder, path_item, instance_var, method='get', mutability=MutabilityEnum.Read)
534+
patch_op = self.generate_operation(cmd_builder, path_item, instance_var, method='patch', mutability=MutabilityEnum.Update)
535+
specific_update_command = self.generate_specific_update_command(path_item, resource, instance_var, cmd_builder, get_op, patch_op)
536+
537+
if specific_update_command is None:
538+
raise exceptions.InvalidAPIUsage(f"Invalid update_by resource: failed to generate specific update: '{resource}'")
539+
540+
command_group.commands.append(specific_update_command)
541+
542+
else:
543+
cmd_builder = CMDBuilder(path=resource.path, method='patch', mutability=MutabilityEnum.Update,
544+
parameterized_host=parameterized_host)
545+
op = self.generate_operation(cmd_builder, path_item, instance_var)
546+
patch_update_command = self.generate_command(path_item, resource, instance_var, cmd_builder, op)
547+
command_group.commands.append(patch_update_command)
478548
# elif update_by == 'GenericAndPatch':
479549
# # TODO: add support for generic and patch merge
480550
# if path_item.get is None or path_item.put is None or path_item.patch is None:

0 commit comments

Comments
 (0)