Skip to content

Commit 5b530d2

Browse files
authored
{Core} Support core bc announcing and Optimize conditional bc usage (#31060)
* Initialize Conditional BC in core * Support Register BC in core * Specify the name as login * Update Conditional BC doc * Fix indent in doc * Fix file name issue * Revert BC announcement in core * Fix style * Support Breaking Changes announced in core * Fix Conditional breaking change style * Add a custom message to deprecate info in Breaking Change method
1 parent 7bc61d4 commit 5b530d2

File tree

4 files changed

+98
-33
lines changed

4 files changed

+98
-33
lines changed

doc/how_to_introduce_breaking_changes.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,26 +274,42 @@ register_other_breaking_change('bar foo', 'During May 2024, another Breaking Cha
274274

275275
**Conditional Breaking Change**
276276

277-
To enhance flexibility, the CLI supports using a designated tag to specify a Breaking Change Pre-announcement. This method avoids reliance on the default automatic warning display and allows the warning to be shown whenever `print_manual_breaking_change` is called.
277+
To enhance flexibility, the CLI supports using a designated tag to specify a Breaking Change Pre-announcement. This method avoids reliance on the default automatic warning display and allows the warning to be shown whenever `print_conditional_breaking_change` is called.
278278

279279
**Note:** We strongly recommend using this method to display breaking change warnings under specific conditions instead of using `logger.warning` directly. This approach enables centralized documentation of breaking changes and assists in automating customer notifications.
280280

281+
* Register
282+
* `tag`: The tag of the conditional breaking change. Use the same tag to print.
283+
* `breaking_change`: Breaking change item to be announced. This should be an instance of `BreakingChange` subclass or a string. You should provide `command_name` when it is a string.
284+
* `command_name`: Used only when input str as a breaking change. This would restrict the scope of breaking change tag.
285+
* Print
286+
* `cli_ctx`: Context object. Pass this as `None` only when specifying `command_name` manually.
287+
* `tag` : The tag of the conditional breaking change. Use the same tag to print.
288+
* `custom_logger`: The logger used to print the warning.
289+
* `command_name`: Used only when `cli_ctx` is `None`.
290+
281291
```python
282292
# src/azure-cli/azure/cli/command_modules/vm/_breaking_change.py
283-
from azure.cli.core.breaking_change import AzCLIOtherChange, register_conditional_breaking_change
293+
from azure.cli.core.breaking_change import register_conditional_breaking_change, AzCLIOtherChange
284294

285295
register_conditional_breaking_change(tag='SpecialBreakingChangeA', breaking_change=AzCLIOtherChange(
286-
'vm create', 'This is special Breaking Change Warning A. This breaking change is happend in "vm create" command.'))
287-
register_conditional_breaking_change(tag='SpecialBreakingChangeB', breaking_change=AzCLIOtherChange(
288-
'vm', 'This is special Breaking Change Warning B. This breaking change is happend in "vm" command group.'))
296+
'vm create', 'This is special Breaking Change Warning A. This breaking change is happend in "vm create" command.'))
297+
register_conditional_breaking_change(
298+
tag='SpecialBreakingChangeB',
299+
breaking_change='This is special Breaking Change Warning B. This breaking change is happend in "vm" command group.',
300+
command_name='vm'
301+
)
289302

290303

291304
# src/azure-cli/azure/cli/command_modules/vm/custom.py
292305
def create_vm(cmd, vm_name, **):
293-
from azure.cli.core.breaking_change import print_conditional_breaking_change
294-
if some_condition:
295-
print_conditional_breaking_change(cmd.cli_ctx, tag='SpecialBreakingChangeA', custom_logger=logger)
296-
print_conditional_breaking_change(cmd.cli_ctx, tag='SpecialBreakingChangeB', custom_logger=logger)
306+
from azure.cli.core.breaking_change import print_conditional_breaking_change
307+
if some_condition:
308+
print_conditional_breaking_change(cmd.cli_ctx, tag='SpecialBreakingChangeA', custom_logger=logger)
309+
print_conditional_breaking_change(cmd.cli_ctx, tag='SpecialBreakingChangeB', custom_logger=logger)
310+
311+
# This is special Breaking Change Warning A. This breaking change is happend in "vm create" command.
312+
# This is special Breaking Change Warning B. This breaking change is happend in "vm" command group.
297313
```
298314

299315
This way, the pre-announcement wouldn't be displayed unless running into the branch, but still could be published in the [Azure CLI Breaking Changes]() article.

src/azure-cli-core/azure/cli/core/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ def load_command_table(self, args):
220220
_load_module_command_loader, _load_extension_command_loader, BLOCKED_MODS, ExtensionCommandSource)
221221
from azure.cli.core.extension import (
222222
get_extensions, get_extension_path, get_extension_modname)
223-
from azure.cli.core.breaking_change import (import_module_breaking_changes, import_extension_breaking_changes)
223+
from azure.cli.core.breaking_change import (
224+
import_core_breaking_changes, import_module_breaking_changes, import_extension_breaking_changes)
224225

225226
def _update_command_table_from_modules(args, command_modules=None):
226227
"""Loads command tables from modules and merge into the main command table.
@@ -416,6 +417,9 @@ def _get_extension_suppressions(mod_loaders):
416417
self.command_group_table.clear()
417418
self.command_table.clear()
418419

420+
# Import announced breaking changes in azure.cli.core._breaking_change.py
421+
import_core_breaking_changes()
422+
419423
command_index = None
420424
# Set fallback=False to turn off command index in case of regression
421425
use_command_index = self.cli_ctx.config.getboolean('core', 'use_command_index', fallback=True)

src/azure-cli-core/azure/cli/core/breaking_change.py

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import abc
66
import argparse
77
import re
8+
import sys
89
from collections import defaultdict
910

1011
from knack.log import get_logger
@@ -270,6 +271,8 @@ def register(self, cli_ctx):
270271
command_group = cli_ctx.invocation.commands_loader.command_group_table[self.command_name]
271272
if not command_group:
272273
loader = self.find_suitable_command_loader(cli_ctx)
274+
if not loader:
275+
return
273276
command_group = loader.command_group(self.command_name)
274277
cli_ctx.invocation.commands_loader.command_group_table[self.command_name] = command_group
275278
if command_group:
@@ -314,8 +317,9 @@ def _register_option_deprecate(self, cli_ctx, arguments, option_name):
314317

315318

316319
class AzCLIDeprecate(BreakingChange):
317-
def __init__(self, cmd, arg=None, target_version=NextBreakingChangeWindow(), **kwargs):
320+
def __init__(self, cmd, arg=None, target_version=NextBreakingChangeWindow(), message=None, **kwargs):
318321
super().__init__(cmd, arg, None, target_version)
322+
self._message = message
319323
self.kwargs = kwargs
320324

321325
@staticmethod
@@ -333,8 +337,8 @@ def _build_message(object_type, target, target_version, redirect):
333337

334338
@property
335339
def message(self):
336-
return self._build_message(self.kwargs.get('object_type'), self.target, self.target_version,
337-
self.kwargs.get('redirect'))
340+
return self._message or self._build_message(self.kwargs.get('object_type'), self.target, self.target_version,
341+
self.kwargs.get('redirect'))
338342

339343
def to_tag(self, cli_ctx, **kwargs):
340344
if self.args:
@@ -345,7 +349,7 @@ def to_tag(self, cli_ctx, **kwargs):
345349
object_type = 'command'
346350
tag_kwargs = {
347351
'object_type': object_type,
348-
'message_func': lambda depr: self._build_message(
352+
'message_func': lambda depr: self._message or self._build_message(
349353
depr.object_type, depr.target, self.target_version, depr.redirect),
350354
'target': self.target
351355
}
@@ -530,6 +534,14 @@ def message(self):
530534
upcoming_breaking_changes = defaultdict(lambda: [])
531535

532536

537+
def import_core_breaking_changes():
538+
try:
539+
from importlib import import_module
540+
import_module('azure.cli.core._breaking_change')
541+
except ImportError:
542+
pass
543+
544+
533545
def import_module_breaking_changes(mod):
534546
try:
535547
from importlib import import_module
@@ -560,8 +572,8 @@ def update_breaking_change_info(cli_ctx, **kwargs): # pylint: disable=unused-ar
560572
cli_ctx.register_event(events.EVENT_INVOKER_POST_CMD_TBL_CREATE, update_breaking_change_info)
561573

562574

563-
def register_deprecate_info(command_name, arg=None, target_version=NextBreakingChangeWindow(), **kwargs):
564-
upcoming_breaking_changes[command_name].append(AzCLIDeprecate(command_name, arg, target_version, **kwargs))
575+
def register_deprecate_info(command_name, arg=None, target_version=NextBreakingChangeWindow(), message=None, **kwargs):
576+
upcoming_breaking_changes[command_name].append(AzCLIDeprecate(command_name, arg, target_version, message, **kwargs))
565577

566578

567579
def register_output_breaking_change(command_name, description, target_version=NextBreakingChangeWindow(), guide=None,
@@ -592,30 +604,60 @@ def register_other_breaking_change(command_name, message, arg=None, target_versi
592604

593605

594606
def register_command_group_deprecate(command_group, redirect=None, hide=None,
595-
target_version=NextBreakingChangeWindow(), **kwargs):
596-
register_deprecate_info(command_group, redirect=redirect, hide=hide, target_version=target_version, **kwargs)
607+
target_version=NextBreakingChangeWindow(), message=None, **kwargs):
608+
register_deprecate_info(command_group, redirect=redirect, hide=hide, target_version=target_version,
609+
message=message, **kwargs)
597610

598611

599612
def register_command_deprecate(command, redirect=None, hide=None,
600-
target_version=NextBreakingChangeWindow(), **kwargs):
601-
register_deprecate_info(command, redirect=redirect, hide=hide, target_version=target_version, **kwargs)
613+
target_version=NextBreakingChangeWindow(), message=None, **kwargs):
614+
register_deprecate_info(command, redirect=redirect, hide=hide, target_version=target_version,
615+
message=message, **kwargs)
602616

603617

604618
def register_argument_deprecate(command, argument, redirect=None, hide=None,
605-
target_version=NextBreakingChangeWindow(), **kwargs):
606-
register_deprecate_info(command, argument, redirect=redirect, hide=hide, target_version=target_version, **kwargs)
607-
608-
609-
def register_conditional_breaking_change(tag, breaking_change):
610-
upcoming_breaking_changes[breaking_change.command_name + '.' + tag].append(breaking_change)
611-
612-
613-
def print_conditional_breaking_change(cli_ctx, tag, custom_logger=None):
614-
command = cli_ctx.invocation.commands_loader.command_name
615-
custom_logger = custom_logger or logger
619+
target_version=NextBreakingChangeWindow(), message=None, **kwargs):
620+
register_deprecate_info(command, argument, redirect=redirect, hide=hide, target_version=target_version,
621+
message=message, **kwargs)
622+
623+
624+
def register_conditional_breaking_change(tag, breaking_change, *, command_name=None):
625+
if command_name and command_name.startswith('az '):
626+
command_name = command_name[3:].strip()
627+
if isinstance(breaking_change, BreakingChange):
628+
command_name = command_name or breaking_change.command_name
629+
upcoming_breaking_changes[command_name + '.' + tag].append(breaking_change)
630+
elif isinstance(breaking_change, str):
631+
command_name = command_name or ''
632+
upcoming_breaking_changes[command_name + '.' + tag].append(AzCLIOtherChange(
633+
cmd=command_name,
634+
message=breaking_change,
635+
))
636+
637+
638+
def print_conditional_breaking_change(cli_ctx, tag, *, custom_logger=None, command_name=None):
639+
"""
640+
Print a breaking change warning message manually.
641+
:param cli_ctx: By default, retrieve the command name from cli_ctx.
642+
:param tag: Use the tag to distinguish different warning messages to be printed in the same command.
643+
Please note, all breaking change items with the same tag from the parent command group will also be printed.
644+
:param custom_logger: Use a custom logger to replace the logger in azure.cli.core.breaking_change.
645+
:param command_name: Specify the command name if not pass in the cli_ctx
646+
"""
647+
command = cli_ctx.invocation.commands_loader.command_name if cli_ctx else command_name
648+
command = command or ''
649+
tag_suffix = '.' + tag if tag is not None else ''
616650

617651
command_comps = command.split()
618652
while command_comps:
619-
for breaking_change in upcoming_breaking_changes.get(' '.join(command_comps) + '.' + tag, []):
620-
custom_logger.warning(breaking_change.message)
653+
for breaking_change in upcoming_breaking_changes.get(' '.join(command_comps) + tag_suffix, []):
654+
if custom_logger:
655+
custom_logger.warning(breaking_change.message)
656+
else:
657+
print(breaking_change.message, file=sys.stderr)
621658
del command_comps[-1]
659+
for breaking_change in upcoming_breaking_changes.get(tag_suffix, []):
660+
if custom_logger:
661+
custom_logger.warning(breaking_change.message)
662+
else:
663+
print(breaking_change.message, file=sys.stderr)

src/azure-cli-core/azure/cli/core/commands/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,9 @@ def _resolve_preview_and_deprecation_warnings(self, cmd, parsed_args):
830830
experimentals.append(ImplicitExperimentalItem(cli_ctx=self.cli_ctx, **experimental_kwargs))
831831

832832
if not self.cli_ctx.only_show_errors:
833+
from azure.cli.core.breaking_change import upcoming_breaking_changes
834+
for bc in upcoming_breaking_changes.get('', []):
835+
print(bc.message, file=sys.stderr)
833836
for d in deprecations:
834837
print(d.message, file=sys.stderr)
835838
for p in previews:

0 commit comments

Comments
 (0)