Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a607feb
refresh for next stable release
msyyc Jun 15, 2026
6e5f072
refresh for next stable release
msyyc Jun 15, 2026
b3d68c3
Update mgmt_sdk_report.md
msyyc Jun 15, 2026
c4c6af0
Merge branch 'main' into refresh-for-next-stable-release
msyyc Jun 24, 2026
6105381
update
msyyc Jun 24, 2026
3f962be
Update mgmt_sdk_report.md
msyyc Jun 24, 2026
46d8d2c
Update mgmt_sdk_report.md
ChenxiJiang333 Jun 25, 2026
63d9760
Update mgmt_sdk_report.md
ChenxiJiang333 Jun 25, 2026
5f40884
Update mgmt_sdk_report.md
ChenxiJiang333 Jun 30, 2026
64b85d8
Update mgmt_sdk_report.md
ChenxiJiang333 Jun 30, 2026
3ef8152
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 3, 2026
5e7dbe0
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 3, 2026
3a8c694
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 3, 2026
e23e25a
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 7, 2026
224de0a
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 7, 2026
19aec7b
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 8, 2026
8d243b5
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 8, 2026
4746a5f
Merge branch 'main' into refresh-for-next-stable-release
msyyc Jul 8, 2026
3feeec4
Update mgmt_sdk_report.md
msyyc Jul 8, 2026
08a1281
Update mgmt_sdk_report.md
msyyc Jul 8, 2026
4f84101
update data
msyyc Jul 8, 2026
3b1a155
update data
msyyc Jul 8, 2026
9e5fe8a
update data
msyyc Jul 8, 2026
831e837
Update mgmt_sdk_report.md
ChenxiJiang333 Jul 8, 2026
10cab66
Merge branch 'main' into refresh-for-next-stable-release
msyyc Jul 9, 2026
81aac5b
update data
msyyc Jul 9, 2026
4b752fa
Merge branch 'main' into refresh-for-next-stable-release
msyyc Jul 10, 2026
499212d
update data
msyyc Jul 10, 2026
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
50 changes: 50 additions & 0 deletions mgmt_sdk_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Stable Management SDK Report


| id | sdk name | api version | beta refresh sdk version | beta refresh pr link | release date | state | stable refresh pr link |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | azure-mgmt-resource | 2025-04-01 | 26.0.0b1 | [#47386](https://github.com/Azure/azure-sdk-for-python/pull/47386) | 2026-06-08 | Done | [47626](https://github.com/Azure/azure-sdk-for-python/pull/47626) |
| 2 | azure-mgmt-loganalytics | 2025-07-01 | 14.0.0b1 | [#47335](https://github.com/Azure/azure-sdk-for-python/pull/47335) | 2026-06-04 | Done | [47629](https://github.com/Azure/azure-sdk-for-python/pull/47629) |
| 3 | azure-mgmt-billing | 2024-04-01 | 8.0.0b1 | [#47277](https://github.com/Azure/azure-sdk-for-python/pull/47277) | 2026-06-02 | Done | [47630](https://github.com/Azure/azure-sdk-for-python/pull/47630) |
| 4 | azure-mgmt-kusto | 2025-02-14 | 4.0.0b1 | [#47278](https://github.com/Azure/azure-sdk-for-python/pull/47278) | 2026-06-02 | Done | [47628](https://github.com/Azure/azure-sdk-for-python/pull/47628) |
| 5 | azure-mgmt-sql | 2025-01-01 | 4.0.0b25 | [#47365](https://github.com/Azure/azure-sdk-for-python/pull/47365) | 2026-06-02 | Done | [47631](https://github.com/Azure/azure-sdk-for-python/pull/47631) |
| 6 | azure-mgmt-datafactory | 2018-06-01 | 10.0.0b1 | [#47198](https://github.com/Azure/azure-sdk-for-python/pull/47198) | 2026-05-28 | Done | [47908](https://github.com/Azure/azure-sdk-for-python/pull/47908) |
| 7 | azure-mgmt-resourcehealth | 2025-05-01 | 1.0.0b7 | [#47187](https://github.com/Azure/azure-sdk-for-python/pull/47187) | 2026-05-28 | Done | [47910](https://github.com/Azure/azure-sdk-for-python/pull/47910) |
| 8 | azure-mgmt-appcontainers | 2026-01-01 | 5.0.0b1 | [#47161](https://github.com/Azure/azure-sdk-for-python/pull/47161) | 2026-05-27 | Done | [47925](https://github.com/Azure/azure-sdk-for-python/pull/47925) |
| 9 | azure-mgmt-databricks | 2026-01-01 | 3.0.0b1 | [#47151](https://github.com/Azure/azure-sdk-for-python/pull/47151) | 2026-05-27 | Done | [47926](https://github.com/Azure/azure-sdk-for-python/pull/47926) |
| 10 | azure-mgmt-storagecache | 2026-01-01 | 4.0.0b1 | [#47154](https://github.com/Azure/azure-sdk-for-python/pull/47154) | 2026-05-27 | Done | [47927](https://github.com/Azure/azure-sdk-for-python/pull/47927) |
| 11 | azure-mgmt-costmanagement | 2025-03-01 | 5.0.0b1 | [#47118](https://github.com/Azure/azure-sdk-for-python/pull/47118) | 2026-05-26 | Done | [47928](https://github.com/Azure/azure-sdk-for-python/pull/47928) |
| 12 | azure-mgmt-databoxedge | 2023-12-01 | 3.0.0b2 | [#47119](https://github.com/Azure/azure-sdk-for-python/pull/47119) | 2026-05-26 | Not Started | |
| 13 | azure-mgmt-redhatopenshift | 2025-07-25 | 4.0.0b1 | [#47124](https://github.com/Azure/azure-sdk-for-python/pull/47124) | 2026-05-26 | Not Started | |
| 14 | azure-mgmt-serialconsole | 2024-07-01 | 1.1.0b2 | [#47125](https://github.com/Azure/azure-sdk-for-python/pull/47125) | 2026-05-26 | Not Started | |
| 15 | azure-mgmt-consumption | 2024-08-01 | 11.0.0b2 | [#47108](https://github.com/Azure/azure-sdk-for-python/pull/47108) | 2026-05-25 | Not Started | |
| 16 | azure-mgmt-cdn | 2025-12-01 | 14.0.0b1 | [#47073](https://github.com/Azure/azure-sdk-for-python/pull/47073) | 2026-05-22 | Not Started | |
| 17 | azure-mgmt-resource-databoundaries | 2024-08-01 | 1.0.0b2 | [#47043](https://github.com/Azure/azure-sdk-for-python/pull/47043) | 2026-05-21 | Not Started | |
| 18 | azure-mgmt-resource-deployments | 2025-04-01 | 1.0.0b2 | [#47044](https://github.com/Azure/azure-sdk-for-python/pull/47044) | 2026-05-21 | Not Started | |
| 19 | azure-mgmt-resource-subscriptions | 2022-12-01 | 1.0.0b2 | [#47042](https://github.com/Azure/azure-sdk-for-python/pull/47042) | 2026-05-21 | Not Started | |
| 20 | azure-mgmt-hybridkubernetes | 2026-05-01 | 2.0.0b1 | [#47018](https://github.com/Azure/azure-sdk-for-python/pull/47018) | 2026-05-20 | Not Started | |
| 21 | azure-mgmt-certificateregistration | 2024-11-01 | 1.0.0b1 | [#46569](https://github.com/Azure/azure-sdk-for-python/pull/46569) | 2026-04-28 | Done | [47764](https://github.com/Azure/azure-sdk-for-python/pull/47764) |
| 22 | azure-mgmt-domainregistration | 2024-11-01 | 1.0.0b1 | [#46570](https://github.com/Azure/azure-sdk-for-python/pull/46570) | 2026-04-28 | Done | [47668](https://github.com/Azure/azure-sdk-for-python/pull/47668) |
| 23 | azure-mgmt-recoveryservicessiterecovery | 2025-08-01 | 2.0.0b1 | [#46275](https://github.com/Azure/azure-sdk-for-python/pull/46275) | 2026-04-27 | Done | [47667](https://github.com/Azure/azure-sdk-for-python/pull/47667) |
| 24 | azure-mgmt-managementgroups | 2023-04-01 | 2.0.0b1 | [#46074](https://github.com/Azure/azure-sdk-for-python/pull/46074) | 2026-04-02 | Done | [47665](https://github.com/Azure/azure-sdk-for-python/pull/47665) |
| 25 | azure-mgmt-networkcloud | 2025-09-01 | 3.0.0b1 | [#46048](https://github.com/Azure/azure-sdk-for-python/pull/46048) | 2026-04-02 | Not Started | |
| 30 | azure-mgmt-sqlvirtualmachine | 2023-10-01 | 1.0.0b7 | | 2026-07-07 | Not Started | |
| 31 | azure-mgmt-servicebus | 2026-01-01 | 10.0.0b2 | | 2026-07-07 | Not Started | |
| 32 | azure-mgmt-peering | 2025-05-01 | 2.0.0b2 | | 2026-07-07 | Done | [#47907](https://github.com/Azure/azure-sdk-for-python/pull/47907) |
| 33 | azure-mgmt-guestconfig | 2024-04-05 | 1.0.0b3 | | 2026-07-07 | Not Started | |
| 34 | azure-mgmt-eventhub | 2026-01-01 | 12.0.0b2 | | 2026-07-07 | Not Started | |
| 35 | azure-mgmt-elastic | 2025-06-01 | 3.0.0b1 | | 2026-07-07 | Not Started | |
| 36 | azure-mgmt-storagesync | 2022-09-01 | 2.0.0b2 | | 2026-07-06 | Not Started | |
| 37 | azure-mgmt-relay | 2024-01-01 | 2.0.0b2 | | 2026-07-06 | Not Started | |
| 38 | azure-mgmt-privatedns | 2024-06-01 | 2.0.0b1 | | 2026-07-06 | Not Started | |
| 39 | azure-mgmt-powerbidedicated | 2021-01-01 | 1.1.0b2 | | 2026-07-06 | Not Started | |
| 40 | azure-mgmt-edgeorder | 2024-02-01 | 3.0.0b1 | | 2026-07-06 | Not Started | |
| 41 | azure-mgmt-dynatrace | 2024-04-24 | 3.0.0b1 | | 2026-07-06 | Not Started | |
| 42 | azure-mgmt-databox | 2025-07-01 | 4.0.0b1 | | 2026-07-06 | Not Started | |
| 43 | azure-mgmt-reservations | 2020-10-25, 2022-11-01 | 3.0.0b1 | | 2026-06-02 | Not Started | |
| 44 | azure-mgmt-automation | 2024-10-23 | 1.1.0b5 | | 2026-05-27 | Not Started | |
| 45 | azure-mgmt-dataprotection | 2026-03-01 | 2.1.0b1 | | 2026-04-07 | Not Started | |
| 46 | azure-mgmt-prometheusrulegroups | 2023-03-01 | 1.0.0b1 | | 2026-07-09 | Not Started | |
| 47 | azure-mgmt-alertprocessingrules | 2021-08-08 | 1.0.0b1 | | 2026-07-09 | Not Started | |
| 48 | azure-mgmt-providerhub | | 1.0.0b1 | | | Not Started | |
| 49 | azure-mgmt-marketplace | | 1.0.0b1 | | | Not Started | |
317 changes: 317 additions & 0 deletions mgmt_stable_sdk_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
#!/usr/bin/env python3
"""Append stable-management-SDK refresh status rows to mgmt_sdk_report.md.

Workflow:
(1) Read management SDK package names from PR 47885's data set.
(2) Keep only packages that exist locally under sdk/*/azure-mgmt-* and have
tsp-location.yaml.
(3) Read apiVersions from _metadata.json. If any api-version contains
"preview", abandon the SDK.
(4) Read the latest SDK version from CHANGELOG.md.
* SDKs with last release dates older than 2026-01-01 are abandoned.
* stable SDK version + stable apiVersions -> already done; do not record.
* beta SDK version + stable apiVersions -> state Not Started.
(5) Append new rows to mgmt_sdk_report.md without editing existing rows.

Requires the GitHub CLI (gh) to be installed and authenticated.
"""

from __future__ import annotations

import argparse
import base64
import json
import re
import shlex
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Sequence

REPO = "Azure/azure-sdk-for-python"
PR_NUMBER = "47885"
ROOT = Path(__file__).resolve().parent
RELEASE_DATE_CUTOFF = "2026-01-01"
SUPPLEMENTAL_CANDIDATE_SDK_NAMES = (
"azure-mgmt-domainservices",
"azure-mgmt-alertprocessingrules",
"azure-mgmt-prometheusrulegroups",
"azure-mgmt-providerhub",
"azure-mgmt-marketplace",
)

SDK_NAME_RE = re.compile(r"\bazure-mgmt-[A-Za-z0-9][A-Za-z0-9_-]*\b")
CHANGELOG_RE = re.compile(r"^##\s+(\S+)\s+\((\d{4}-\d{2}-\d{2})\)")
PR_NUMBER_RE = re.compile(r"\(#(\d+)\)")
REPORT_ROW_RE = re.compile(r"^\|\s*(\d+)\s*\|\s*(azure-mgmt-[^|\s]+)")


@dataclass(frozen=True)
class PackageRow:
name: str
api_versions: list[str]
sdk_version: str
release_date: str
state: str
beta_refresh_pr: str = ""
stable_refresh_pr: str = ""


def log(message: str) -> None:
print(message, file=sys.stderr, flush=True)


def run_command(args: Sequence[str]) -> subprocess.CompletedProcess[str]:
return subprocess.run(args, cwd=ROOT, capture_output=True, text=True)


def run_gh_json(args: Sequence[str]) -> dict:
result = run_command(["gh", *args])
if result.returncode != 0:
raise RuntimeError(result.stderr.strip() or result.stdout.strip())
return json.loads(result.stdout)


def fetch_pr_file(pr_number: str, file_name: str) -> str:
pr_data = run_gh_json(["pr", "view", pr_number, "--repo", REPO, "--json", "headRefName,headRepositoryOwner,headRepository"])
owner = pr_data["headRepositoryOwner"]["login"]
repo = pr_data["headRepository"]["name"]
ref = pr_data["headRefName"]
contents = run_gh_json(["api", f"repos/{owner}/{repo}/contents/{file_name}?ref={ref}"])
encoded = "".join(str(contents["content"]).splitlines())
return base64.b64decode(encoded).decode("utf-8")


def sdk_names_from_data(data_text: str) -> list[str]:
names: list[str] = []
seen: set[str] = set()
for line in data_text.splitlines():
stripped = line.strip()
if not stripped or ("service" in stripped.lower() and "folder" in stripped.lower()):
continue
for part in shlex.split(stripped):
if SDK_NAME_RE.fullmatch(part) and part not in seen:
seen.add(part)
names.append(part)
return names


def sdk_names_from_result(result_text: str) -> list[str]:
names: list[str] = []
seen: set[str] = set()
for line in result_text.splitlines():
if not line.startswith("|") or "azure-mgmt" not in line:
continue
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
if len(cells) < 4:
continue
name = cells[3]
if SDK_NAME_RE.fullmatch(name) and name not in seen:
seen.add(name)
names.append(name)
return names


def get_candidate_sdk_names(pr_number: str) -> list[str]:
data_names = sdk_names_from_data(fetch_pr_file(pr_number, "data.txt"))
try:
result_names = sdk_names_from_result(fetch_pr_file(pr_number, "result.md"))
except Exception as ex: # pylint: disable=broad-except
log(f"Could not read result.md from PR {pr_number}; using explicit data.txt names only: {ex}")
result_names = []

names: list[str] = []
seen: set[str] = set()
for name in [*result_names, *data_names, *SUPPLEMENTAL_CANDIDATE_SDK_NAMES]:
if name not in seen:
seen.add(name)
names.append(name)
return names


def package_dir(sdk_name: str) -> Path | None:
matches = sorted((ROOT / "sdk").glob(f"*/{sdk_name}"))
for match in matches:
if (match / "tsp-location.yaml").is_file():
return match
return None


def get_api_versions(pkg_dir: Path) -> list[str]:
metadata = pkg_dir / "_metadata.json"
if not metadata.is_file():
return []
data = json.loads(metadata.read_text(encoding="utf-8"))
api_versions = data.get("apiVersions")
if isinstance(api_versions, dict):
values = api_versions.values()
elif isinstance(api_versions, list):
values = api_versions
else:
values = [data.get("apiVersion")]
return sorted({str(value) for value in values if value})


def has_preview_api_version(api_versions: Sequence[str]) -> bool:
return any("preview" in version.lower() for version in api_versions)


def get_version_and_release_date(pkg_dir: Path) -> tuple[str, str]:
changelog = pkg_dir / "CHANGELOG.md"
if not changelog.is_file():
return "", ""
for line in changelog.read_text(encoding="utf-8").splitlines():
match = CHANGELOG_RE.match(line.strip())
if match:
return match.group(1), match.group(2)
return "", ""


def is_beta_version(version: str) -> bool:
return bool(re.search(r"\d+b\d*", version.lower()))


def find_refresh_pr(pkg_dir: Path) -> str:
rel_path = pkg_dir.relative_to(ROOT).as_posix()
result = run_command(["git", "log", "-n", "80", "--format=%s", "--", rel_path])
if result.returncode != 0:
log(f" ! git log failed for {rel_path}: {result.stderr.strip()}")
return ""

for subject in result.stdout.splitlines():
match = PR_NUMBER_RE.search(subject)
if match and "refresh" in subject.lower():
return match.group(1)
return ""


def build_rows(sdk_names: Sequence[str], limit: int = 0) -> list[PackageRow]:
rows: list[PackageRow] = []
processed = 0
for sdk_name in sdk_names:
if limit and processed >= limit:
break
processed += 1
log(f"Processing {sdk_name} ...")

pkg_dir = package_dir(sdk_name)
if not pkg_dir:
log(" - skip: local package with tsp-location.yaml not found")
continue

api_versions = get_api_versions(pkg_dir)
if not api_versions:
log(" - skip: _metadata.json/apiVersions not found")
continue
if has_preview_api_version(api_versions):
log(f" - skip: preview api-version ({', '.join(api_versions)})")
continue

sdk_version, release_date = get_version_and_release_date(pkg_dir)
if not sdk_version:
log(" - skip: no version found in CHANGELOG.md")
continue
if release_date and release_date < RELEASE_DATE_CUTOFF:
log(f" - skip: last release date {release_date} is older than {RELEASE_DATE_CUTOFF}")
continue

if is_beta_version(sdk_version):
rows.append(
PackageRow(
name=sdk_name,
api_versions=api_versions,
sdk_version=sdk_version,
release_date=release_date,
state="Not Started",
)
)
log(f" + keep: beta SDK version ({sdk_version}); state=Not Started")
else:
log(f" - skip: stable SDK version ({sdk_version}) is already done")
return rows


def existing_report_state(report_path: Path) -> tuple[int, set[str]]:
if not report_path.is_file():
return 0, set()
max_id = 0
existing_names: set[str] = set()
for line in report_path.read_text(encoding="utf-8").splitlines():
match = REPORT_ROW_RE.match(line)
if not match:
continue
max_id = max(max_id, int(match.group(1)))
existing_names.add(match.group(2))
return max_id, existing_names


def markdown_link(pr_number: str) -> str:
if not pr_number:
return ""
return f"[#{pr_number}](https://github.com/{REPO}/pull/{pr_number})"


def table_value(value: str) -> str:
return value.replace("|", "\\|")


def format_row(row_id: int, row: PackageRow) -> str:
return "| " + " | ".join(
[
str(row_id),
table_value(row.name),
table_value(", ".join(row.api_versions)),
table_value(row.sdk_version),
markdown_link(row.beta_refresh_pr),
table_value(row.release_date),
table_value(row.state),
markdown_link(row.stable_refresh_pr),
]
) + " |"


def append_rows(report_path: Path, rows: Sequence[PackageRow], include_existing: bool) -> int:
max_id, existing_names = existing_report_state(report_path)
rows_to_append = [row for row in rows if include_existing or row.name not in existing_names]
if not rows_to_append:
log("No new rows to append.")
return 0

existing_text = report_path.read_text(encoding="utf-8") if report_path.is_file() else ""
prefix = "" if not existing_text or existing_text.endswith("\n") else "\n"
lines = [format_row(max_id + index, row) for index, row in enumerate(rows_to_append, start=1)]
with report_path.open("a", encoding="utf-8") as report_file:
report_file.write(prefix + "\n".join(lines) + "\n")
return len(rows_to_append)


def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--pr", default=PR_NUMBER, help=f"PR number to read package data from. Defaults to {PR_NUMBER}.")
parser.add_argument("--limit", type=int, default=0, help="Process only the first N candidate SDKs. 0 = all.")
parser.add_argument("--output", type=Path, default=ROOT / "mgmt_sdk_report.md", help="Report file to append to.")
parser.add_argument("--include-existing", action="store_true", help="Append rows even when the SDK name already exists in the report.")
parser.add_argument("--dry-run", action="store_true", help="Print rows to stdout instead of appending to the report.")
return parser.parse_args(argv)


def main(argv: Sequence[str] | None = None) -> int:
args = parse_args(argv)
sdk_names = get_candidate_sdk_names(args.pr)
log(f"Discovered {len(sdk_names)} candidate SDK name(s) from PR {args.pr}.")
rows = build_rows(sdk_names, args.limit)
rows.sort(key=lambda row: (row.release_date, row.name), reverse=True)

if args.dry_run:
for index, row in enumerate(rows, start=1):
print(format_row(index, row))
return 0

appended = append_rows(args.output, rows, args.include_existing)
log(f"Appended {appended} row(s) to {args.output}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading