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 3 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
55 changes: 44 additions & 11 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 **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 **2** sprints ahead of Breaking Change Window.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually I suppose sprint will vary across team, but monthly basis is often adopted.

If we put as 1-month policy above(Line 32), we might keep it aligned here, rather than 2 sprints, right?

* **Service Owned Module**
* Service Team should create a Pull Request that adds the pre-announcement at least **2** sprints ahead of Breaking Change Window.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

* The pre-announcement should be released at least **2** sprints ahead of Breaking Change Window.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

* 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
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