Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

{Core} Extension Breaking Change Announcement Instruction #30975

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
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
70 changes: 55 additions & 15 deletions doc/how_to_introduce_breaking_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,57 @@ You could find the next Breaking Change Release plan in our [milestones](https:/
>
> Please note that providing the required info for assessment does not mean it will be assured to be green-lighted for breaking changes. Team will still make the decision based on the overall impact.

### Pre-announce Breaking Changes
### Ahead-of-1-Month Pre-announcement Policy

All breaking changes **must** be pre-announced two sprints ahead Release. It give users the buffer time ahead to mitigate for better command experience. There are two approaches to inform both interactive users and automatic users about the breaking changes.
All breaking changes **must** be pre-announced **30** days(usually **2** sprints for modules in Core CLI) ahead Release. It gives users the buffer time ahead to mitigate for better command experience. There are two approaches to inform both interactive users and automatic users about the breaking changes.

1. (**Mandatory**) Breaking Changes must be pre-announced through Warning Log while executing.
2. (*Automatic*) Breaking Changes would be collected automatically and listed in [Upcoming Breaking Change](https://learn.microsoft.com/en-us/cli/azure/upcoming-breaking-changes) Document.

### Breaking Changes in Extensions

All breaking changes in GA extensions **must** be pre-announced at least **30** days prior to their Release.

Extensions don't need to follow the breaking change window. However, we still strongly recommend releasing breaking changes only in breaking change windows along with Core Azure CLI.

```text
[GA Release with Breaking Change Pre-Announcement]
├─ Must include complete Breaking Change Information
└─┬─ [Minimum 30-day Announcement Period] ────────────────┐
│ │
│ Allow releases during this period: │
│ - Other unrelated GA versions (vX.(Y+1), v(X+1).Y) │
│ - Multiple preview releases (Beta) │
│ │
▼ ▼
[GA Release Containing Breaking Changes]
(Must fulfill 30-day announcement requirement)
```

## Workflow

### Overview
### CLI Workflow Overview

* CLI Owned Module
* Service Team should create an Issue that requests CLI Team to create the pre-announcement several sprints ahead Breaking Change Window. The issue should include the label `Breaking Change`. The CLI team will look at the issue and evaluate if it will be accepted in the next breaking change release.
* **CLI Owned Module**
* Service Team should create an Issue that requests CLI Team to create the pre-announcement at least **1** month(usually **2** sprints) ahead of Breaking Change Window. The issue should include the label `Breaking Change`. The CLI team will look at the issue and evaluate if it will be accepted in the next breaking change release.
* Please ensure sufficient time for CLI Team to finish the pre-announcement.
* The pre-announcement should be released ahead of Breaking Change Window.
* Service Owned Module
* Service Team should create a Pull Request that create the pre-announcement several sprints ahead Breaking Change Window.
* The pre-announcement should be released ahead of Breaking Change Window.
* The pre-announcement should be released at least **1** month(usually **2** sprints) ahead of Breaking Change Window.
* **Service Owned Module**
* Service Team should create a Pull Request that adds the pre-announcement at least **1** month(usually **2** sprints) ahead of Breaking Change Window.
* The pre-announcement should be released at least **1** month(usually **2** sprints) ahead of Breaking Change Window.
* After releasing the pre-announcement, a pipeline would be triggered, and the Upcoming Breaking Change Documentation would be updated.
* At the start of Breaking Change window, the CLI team would notify Service Teams to adopt Breaking Changes.
* Breaking Changes should be adopted within Breaking Change Window. Any unfinished pre-announcements of breaking changes targeting this release will be deleted by the CLI team.

### Extensions Workflow Overview

* Service Team should create a Pull Request that includes the pre-announcement.
* The pre-announcement should be released after merged.
* After releasing the pre-announcement, a pipeline would be triggered, and the Upcoming Breaking Change Documentation would be updated.
* After 30 days, the Pull Request that contains the actual breaking changes could be merged and released.

### Pre-announce Breaking Changes

The breaking change pre-announcement must be released at least two sprints before the breaking change itself. It is strongly recommended to follow the best practice of providing the new behavior along with the pre-announcement. This allows customers to take action as soon as they discover the pre-announcement.
Expand All @@ -72,7 +101,7 @@ You can then pre-announce breaking changes for different command groups or comma
from azure.cli.core.breaking_change import register_required_flag_breaking_change, register_default_value_breaking_change, register_other_breaking_change

register_required_flag_breaking_change('bar foo', '--name')
register_default_value_breaking_change('bar foo baz', '--foobar', 'A', 'B')
register_default_value_breaking_change('bar foo baz', '--foobar', 'A', 'B', target_version='May 2025')
register_other_breaking_change('bar foo baz', 'During May 2024, another Breaking Change would happen in Build Event.')
```

Expand All @@ -84,7 +113,7 @@ az bar foo baz

# =====Warning output=====
# The argument '--name' will become required in next breaking change release(2.61.0).
# The default value of '--foobar' will be changed to 'B' from 'A' in next breaking change release(2.61.0).
# The default value of '--foobar' will be changed to 'B' from 'A' in May 2025.
# During May 2024, another Breaking Change would happen in Build Event.
```

Expand Down Expand Up @@ -132,6 +161,8 @@ from azure.cli.core.breaking_change import register_argument_deprecate

register_argument_deprecate('bar foo', '--name', target_version='2.70.0')
# Warning Message: Option `--name` has been deprecated and will be removed in 2.70.0.
register_argument_deprecate('bar foo', '--name', target_version='May 2025')
# Warning Message: Option `--name` has been deprecated and will be removed in May 2025.
```

**Rename**
Expand All @@ -143,6 +174,8 @@ from azure.cli.core.breaking_change import register_argument_deprecate

register_argument_deprecate('bar foo', '--name', '--new-name')
# Warning Message: Option `--name` has been deprecated and will be removed in next breaking change release(2.67.0). Use `--new-name` instead.
register_argument_deprecate('bar foo', '--name', '--new-name', target_version='May 2025')
# Warning Message: Option `--name` has been deprecated and will be removed in May 2025. Use `--new-name` instead.
```

**Output Change**
Expand Down Expand Up @@ -197,6 +230,10 @@ from azure.cli.core.breaking_change import register_default_value_breaking_chang

register_default_value_breaking_change('bar foo', '--type', 'TypeA', 'TypeB')
# The default value of `--type` will be changed to `TypeB` from `TypeA` in next breaking change release(2.61.0).

# azure-cli-extensions/src/ext/azext_ext/_breaking_change.py
register_default_value_breaking_change('bar foo', '--type', 'TypeA', 'TypeB', target_version='3.x.x')
# The default value of `--type` will be changed to `TypeB` from `TypeA` in 3.x.x.
```

**Be Required**
Expand All @@ -214,6 +251,9 @@ from azure.cli.core.breaking_change import register_required_flag_breaking_chang

register_required_flag_breaking_change('bar foo', '--type')
# The argument `--type` will become required in next breaking change release(2.61.0).

register_required_flag_breaking_change('bar foo', '--type', target_version='May 2024')
# The argument `--type` will become required in May 2024.
```

**Other Changes**
Expand All @@ -222,8 +262,8 @@ Declare other custom-breaking changes that do not fall into the predefined categ

* `command`: REQUIRED: The name of the command.
* `message`: REQUIRED: The short-summary description of the breaking change. The description displays in warning messages.
* `arg`: REQUIRED: The name of the argument associated with the breaking change. If arg is not None, the warning message will only be displayed when the argument is used.
* `target_version`: REQUIRED: The version in which the breaking change will occur. By default, this is set to the next breaking change window. This information is published in the [Azure CLI Breaking Changes](https://learn.microsoft.com/en-us/cli/azure/upcoming-breaking-changes) article, but does NOT display in the warning message.
* `arg`: The name of the argument associated with the breaking change. If arg is not None, the warning message will only be displayed when the argument is used.
* `target_version`: REQUIRED: The version in which the breaking change will occur. By default, this is set to the next breaking change window. This information affect the visibility in the [Azure CLI Breaking Changes](https://learn.microsoft.com/en-us/cli/azure/upcoming-breaking-changes) article, but does NOT display in the warning message.

```python
from azure.cli.core.breaking_change import register_other_breaking_change
Expand All @@ -239,8 +279,8 @@ To enhance flexibility, the CLI supports using a designated tag to specify a Bre
**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.

```python
# src/azure-cli/azure/cli/command_modules/vm/custom.py
from azure.cli.core.breaking_change import register_conditional_breaking_change, AzCLIOtherChange
# src/azure-cli/azure/cli/command_modules/vm/_breaking_change.py
from azure.cli.core.breaking_change import register_conditional_breaking_change

register_conditional_breaking_change(tag='SpecialBreakingChangeA', breaking_change=(
'vm create', 'This is special Breaking Change Warning A. This breaking change is happend in "vm create" command.'))
Expand Down
25 changes: 22 additions & 3 deletions src/azure-cli-core/azure/cli/core/breaking_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# --------------------------------------------------------------------------------------------
import abc
import argparse
import re
from collections import defaultdict

from knack.log import get_logger
Expand All @@ -14,6 +15,7 @@
logger = get_logger()

NEXT_BREAKING_CHANGE_RELEASE = '2.73.0'
NEXT_BREAKING_CHANGE_DATE = 'May 2025'
DEFAULT_BREAKING_CHANGE_TAG = '[Breaking Change]'


Expand Down Expand Up @@ -151,9 +153,12 @@ def version(self):
class NextBreakingChangeWindow(TargetVersion):
def __str__(self):
next_breaking_change_version = _next_breaking_change_version()
message = 'in next breaking change release'
if next_breaking_change_version:
return f'in next breaking change release({next_breaking_change_version})'
return 'in next breaking change release'
message += f'({next_breaking_change_version})'
if NEXT_BREAKING_CHANGE_DATE:
message += f' scheduled for {NEXT_BREAKING_CHANGE_DATE}'
return message

def version(self):
return _next_breaking_change_version()
Expand All @@ -171,6 +176,18 @@ def version(self):
return self._version


# pylint: disable=too-few-public-methods
class NonVersion(TargetVersion):
def __init__(self, msg):
self._msg = msg

def __str__(self):
return f'in {self._msg}'

def version(self):
return None


# pylint: disable=too-few-public-methods
class UnspecificVersion(TargetVersion):
def __str__(self):
Expand All @@ -192,8 +209,10 @@ def __init__(self, cmd, arg=None, target=None, target_version=None):
self.target = target if target else '/'.join(self.args) if self.args else self.cmd
if isinstance(target_version, TargetVersion):
self._target_version = target_version
elif isinstance(target_version, str):
elif isinstance(target_version, str) and re.match(r'\d+.\d+.\d+', target_version):
self._target_version = ExactVersion(target_version)
elif isinstance(target_version, str):
self._target_version = NonVersion(target_version)
else:
self._target_version = UnspecificVersion()

Expand Down
21 changes: 14 additions & 7 deletions src/azure-cli-core/azure/cli/core/tests/test_breaking_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,15 @@ def test_command_deprecate(self):
self.assertIn('[Deprecated]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', 'May 2024')
def test_argument_deprecate(self):
from contextlib import redirect_stderr, redirect_stdout

from azure.cli.core.breaking_change import register_argument_deprecate

register_argument_deprecate('test group cmd', argument='arg1', redirect='arg2')
warning = ("Argument 'arg1' has been deprecated and will be removed in next breaking change release(3.0.0). "
"Use 'arg2' instead.")
warning = ("Argument 'arg1' has been deprecated and will be removed in next breaking change release(3.0.0) "
"scheduled for May 2024. Use 'arg2' instead.")
cli = DummyCli(commands_loader_cls=TestCommandsLoader)

captured_err = io.StringIO()
Expand All @@ -156,14 +157,15 @@ def test_argument_deprecate(self):
self.assertIn('[Deprecated]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', 'May 2024')
def test_option_deprecate(self):
from contextlib import redirect_stderr, redirect_stdout

from azure.cli.core.breaking_change import register_argument_deprecate

register_argument_deprecate('test group cmd', argument='--arg1', redirect='--arg1-alias')
warning = ("Option '--arg1' has been deprecated and will be removed in next breaking change release(3.0.0). "
"Use '--arg1-alias' instead.")
warning = ("Option '--arg1' has been deprecated and will be removed in next breaking change release(3.0.0) "
"scheduled for May 2024. Use '--arg1-alias' instead.")
cli = DummyCli(commands_loader_cls=TestCommandsLoader)

captured_err = io.StringIO()
Expand All @@ -180,6 +182,7 @@ def test_option_deprecate(self):
self.assertIn(' --arg1 [Deprecated]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', None)
def test_be_required(self):
from contextlib import redirect_stderr, redirect_stdout

Expand Down Expand Up @@ -207,6 +210,7 @@ def test_be_required(self):
self.assertIn('[Breaking Change]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', None)
def test_default_change(self):
from contextlib import redirect_stderr, redirect_stdout

Expand Down Expand Up @@ -236,15 +240,16 @@ def test_default_change(self):
self.assertIn('[Breaking Change]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', 'May 2024')
def test_output_change(self):
from contextlib import redirect_stderr, redirect_stdout

from azure.cli.core.breaking_change import register_output_breaking_change

register_output_breaking_change('test group cmd', description="The output of 'test group cmd' "
"would be changed.")
warning = ("The output will be changed in next breaking change release(3.0.0). The output of 'test group cmd' "
"would be changed.")
warning = ("The output will be changed in next breaking change release(3.0.0) scheduled for May 2024. "
"The output of 'test group cmd' would be changed.")
cli = DummyCli(commands_loader_cls=TestCommandsLoader)

captured_err = io.StringIO()
Expand All @@ -265,13 +270,14 @@ def test_output_change(self):
self.assertIn('[Breaking Change]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', 'May 2024')
def test_logic_change(self):
from contextlib import redirect_stderr, redirect_stdout

from azure.cli.core.breaking_change import register_logic_breaking_change

register_logic_breaking_change('test group cmd', summary="Logic Change Summary")
warning = "Logic Change Summary in next breaking change release(3.0.0)."
warning = "Logic Change Summary in next breaking change release(3.0.0) scheduled for May 2024."
cli = DummyCli(commands_loader_cls=TestCommandsLoader)

captured_err = io.StringIO()
Expand All @@ -292,6 +298,7 @@ def test_logic_change(self):
self.assertIn('[Breaking Change]', captured_output.getvalue())

@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_RELEASE', '3.0.0')
@mock.patch('azure.cli.core.breaking_change.NEXT_BREAKING_CHANGE_DATE', None)
def test_multi_breaking_change(self):
from contextlib import redirect_stderr, redirect_stdout

Expand Down