diff --git a/.gitignore b/.gitignore index c5ca572c5..a9d13d3ca 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,4 @@ logs-debug/ docs/**/log/* docs/**/test.sh.run.log +/.venv/ diff --git a/scripts/release/calculate_next_version.py b/scripts/release/calculate_next_version.py new file mode 100644 index 000000000..5545b4355 --- /dev/null +++ b/scripts/release/calculate_next_version.py @@ -0,0 +1,46 @@ +import argparse +import pathlib + +from git import Repo + +from scripts.release.release_notes import calculate_next_version_with_changelog + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--path", + action="store", + default=".", + type=pathlib.Path, + help="Path to the Git repository. Default is the current directory '.'", + ) + parser.add_argument( + "--changelog_path", + default="changelog/", + action="store", + type=str, + help="Path to the changelog directory relative to the repository root. Default is 'changelog/'", + ) + parser.add_argument( + "--initial_commit_sha", + action="store", + type=str, + help="SHA of the initial commit to start from if no previous version tag is found.", + ) + parser.add_argument( + "--initial_version", + default="1.0.0", + action="store", + type=str, + help="Version to use if no previous version tag is found. Default is '1.0.0'", + ) + parser.add_argument("--output", "-o", type=pathlib.Path) + args = parser.parse_args() + + repo = Repo(args.path) + + version, _ = calculate_next_version_with_changelog( + repo, args.changelog_path, args.initial_commit_sha, args.initial_version + ) + + print(version) diff --git a/scripts/release/changelog.py b/scripts/release/changelog.py new file mode 100644 index 000000000..2de2d59eb --- /dev/null +++ b/scripts/release/changelog.py @@ -0,0 +1,63 @@ +import os +from enum import StrEnum + +from git import Commit, Repo + +CHANGELOG_PATH = "changelog/" + +PRELUDE_ENTRIES = ["prelude"] +BREAKING_CHANGE_ENTRIES = ["breaking_change", "breaking", "major"] +FEATURE_ENTRIES = ["feat", "feature"] +BUGFIX_ENTRIES = ["fix", "bugfix", "hotfix", "patch"] + + +class ChangeType(StrEnum): + PRELUDE = "prelude" + BREAKING = "breaking" + FEATURE = "feature" + FIX = "fix" + OTHER = "other" + + +def get_changelog_entries( + previous_version_commit: Commit, + repo: Repo, + changelog_sub_path: str, +) -> list[tuple[ChangeType, str]]: + changelog = [] + + # Compare previous version commit with current working tree + diff_index = previous_version_commit.diff(other=repo.head.commit, paths=changelog_sub_path) + + # No changes since the previous version + if not diff_index: + return changelog + + # Traverse added Diff objects only (change type 'A' for added files) + for diff_item in diff_index.iter_change_type("A"): + file_path = diff_item.b_path + file_name = os.path.basename(file_path) + change_type = get_change_type(file_name) + + abs_file_path = os.path.join(repo.working_dir, file_path) + with open(abs_file_path, "r") as file: + file_content = file.read() + + changelog.append((change_type, file_content)) + + return changelog + + +def get_change_type(file_name: str) -> ChangeType: + """Extract the change type from the file name.""" + + if any(entry in file_name.lower() for entry in PRELUDE_ENTRIES): + return ChangeType.PRELUDE + if any(entry in file_name.lower() for entry in BREAKING_CHANGE_ENTRIES): + return ChangeType.BREAKING + elif any(entry in file_name.lower() for entry in FEATURE_ENTRIES): + return ChangeType.FEATURE + elif any(entry in file_name.lower() for entry in BUGFIX_ENTRIES): + return ChangeType.FIX + else: + return ChangeType.OTHER diff --git a/scripts/release/changelog_test.py b/scripts/release/changelog_test.py new file mode 100644 index 000000000..c2e3e0b61 --- /dev/null +++ b/scripts/release/changelog_test.py @@ -0,0 +1,26 @@ +from changelog import ChangeType, get_change_type + + +def test_get_change_type(): + + # Test prelude + assert get_change_type("20250502_prelude_release_notes.md") == ChangeType.PRELUDE + + # Test breaking changes + assert get_change_type("20250101_breaking_change_api_update.md") == ChangeType.BREAKING + assert get_change_type("20250508_breaking_remove_deprecated.md") == ChangeType.BREAKING + assert get_change_type("20250509_major_schema_change.md") == ChangeType.BREAKING + + # Test features + assert get_change_type("20250509_feature_new_dashboard.md") == ChangeType.FEATURE + assert get_change_type("20250511_feat_add_metrics.md") == ChangeType.FEATURE + + # Test fixes + assert get_change_type("20251210_fix_olm_missing_images.md") == ChangeType.FIX + assert get_change_type("20251010_bugfix_memory_leak.md") == ChangeType.FIX + assert get_change_type("20250302_hotfix_security_issue.md") == ChangeType.FIX + assert get_change_type("20250301_patch_typo_correction.md") == ChangeType.FIX + + # Test other + assert get_change_type("20250520_docs_update_readme.md") == ChangeType.OTHER + assert get_change_type("20250610_refactor_codebase.md") == ChangeType.OTHER diff --git a/scripts/release/conftest.py b/scripts/release/conftest.py new file mode 100644 index 000000000..94b8bd66e --- /dev/null +++ b/scripts/release/conftest.py @@ -0,0 +1,162 @@ +import os +import shutil +import tempfile + +from _pytest.fixtures import fixture +from git import Repo + +from scripts.release.changelog import CHANGELOG_PATH + + +@fixture(scope="session") +def git_repo(change_log_path: str = CHANGELOG_PATH) -> Repo: + """ + Create a temporary git repository for testing. + Visual representation of the repository structure is in test_git_repo.mmd (mermaid/gitgraph https://mermaid.js.org/syntax/gitgraph.html). + Whenever you modify or add new commits please update the git graph as well. + """ + + repo_dir = tempfile.mkdtemp() + repo = Repo.init(repo_dir) + changelog_path = os.path.join(repo_dir, change_log_path) + os.mkdir(changelog_path) + + ## First commit and 1.0.0 tag + repo.git.checkout("-b", "master") + new_file = create_new_file(repo_dir, "new-file.txt", "Initial content\n") + repo.index.add(new_file) + repo.index.commit("initial commit") + changelog_file = add_file(repo_dir, "changelog/20250506_prelude_mck.md") + repo.index.add(changelog_file) + repo.index.commit("release notes prelude MCK") + repo.create_tag("1.0.0", message="Initial release") + + ## Bug fixes and 1.0.1 tag + file_name = create_new_file(repo_dir, "another-file.txt", "Added more content\n") + changelog_file = add_file(repo_dir, "changelog/20250510_fix_olm_missing_images.md") + repo.index.add([file_name, changelog_file]) + repo.index.commit("olm missing images fix") + changelog_file = add_file(repo_dir, "changelog/20250510_fix_watched_list_in_helm.md") + repo.index.add(changelog_file) + repo.index.commit("fix watched list in helm") + repo.create_tag("1.0.1", message="Bug fix release") + + ## Private search preview and 1.1.0 tag (with changelog fix) + changelog_file = add_file(repo_dir, "changelog/20250523_feature_community_search_preview.md") + repo.index.add(changelog_file) + repo.index.commit("private search preview") + changelog_file = add_file( + repo_dir, + "changelog/20250523_feature_community_search_preview_UPDATED.md", + "changelog/20250523_feature_community_search_preview.md", + ) + repo.index.add(changelog_file) + repo.index.commit("add limitations in changelog for private search preview") + repo.create_tag("1.1.0", message="Public search preview release") + + ## OIDC release and 1.2.0 tag + changelog_file = add_file(repo_dir, "changelog/20250610_feature_oidc.md") + repo.index.add(changelog_file) + repo.index.commit("OIDC integration") + repo.create_tag("1.2.0", message="OIDC integration release") + + ## Static architecture release and 2.0.0 tag + changelog_file = add_file(repo_dir, "changelog/20250612_breaking_static_as_default.md") + repo.index.add(changelog_file) + repo.index.commit("Static architecture as default") + changelog_file = add_file(repo_dir, "changelog/20250616_feature_om_no_service_mesh.md") + repo.index.add(changelog_file) + repo.index.commit("Ops Manager no service mesh support") + changelog_file_1 = add_file(repo_dir, "changelog/20250620_fix_static_container.md") + changelog_file_2 = add_file(repo_dir, "changelog/20250622_fix_external_access.md") + repo.index.add([changelog_file_1, changelog_file_2]) + fix_commit = repo.index.commit("Fixes for static architecture") + changelog_file = add_file(repo_dir, "changelog/20250623_prelude_static.md") + repo.index.add(changelog_file) + repo.index.commit("Release notes prelude for static architecture") + repo.create_tag("2.0.0", message="Static architecture release") + + ## Create release-1.x branch and backport fix + repo.git.checkout("1.2.0") + release_1_x_branch = repo.create_head("release-1.x").checkout() + repo.git.cherry_pick(fix_commit.hexsha) + repo.create_tag("1.2.1", message="Bug fix release") + + ## Bug fixes and 2.0.1 tag + repo.git.checkout("master") + file_name = create_new_file(repo_dir, "bugfix-placeholder.go", "Bugfix in go\n") + changelog_file = add_file(repo_dir, "changelog/20250701_fix_placeholder.md") + repo.index.add([file_name, changelog_file]) + fix_commit_1 = repo.index.commit("placeholder fix") + changelog_file = add_file(repo_dir, "changelog/20250702_fix_clusterspeclist_validation.md") + repo.index.add(changelog_file) + fix_commit_2 = repo.index.commit("fix clusterspeclist validation") + repo.create_tag("2.0.1", message="Bug fix release") + + ## Backport fixes to release-1.x branch + repo.git.checkout(release_1_x_branch) + repo.git.cherry_pick(fix_commit_1.hexsha) + repo.git.cherry_pick(fix_commit_2.hexsha) + repo.create_tag("1.2.2", message="Bug fix release") + + ## Bug fix and 2.0.2 tag + repo.git.checkout("master") + changelog_file = add_file(repo_dir, "changelog/20250707_fix_proxy_env_var.md") + repo.index.add(changelog_file) + fix_commit = repo.index.commit("fix proxy env var validation") + repo.create_tag("2.0.2", message="Bug fix release") + + ## Backport fixes to release-1.x branch + repo.git.checkout(release_1_x_branch) + repo.git.cherry_pick(fix_commit) + repo.create_tag("1.2.3", message="Bug fix release") + + ## Static architecture release and 3.0.0 tag + repo.git.checkout("master") + changelog_file_1 = add_file(repo_dir, "changelog/20250710_breaking_mongodbmulti_refactor.md") + changelog_file_2 = add_file(repo_dir, "changelog/20250710_prelude_mongodbmulti_refactor.md") + repo.index.add([changelog_file_1, changelog_file_2]) + repo.index.commit("Moved MongoDBMulti into single MongoDB resource") + changelog_file = add_file(repo_dir, "changelog/20250711_feature_public_search.md") + repo.index.add(changelog_file) + repo.index.commit("Public search support") + changelog_file = add_file(repo_dir, "changelog/20250712_fix_mongodbuser_phase.md") + repo.index.add(changelog_file) + fix_commit = repo.index.commit("MongoDBUser phase update fix") + repo.create_tag("3.0.0", message="MongoDBMulti integration with MongoDB resource") + + ## Create release-2.x branch and backport fix + repo.git.checkout("2.0.2") + release_2_x_branch = repo.create_head("release-2.x").checkout() + repo.git.cherry_pick(fix_commit.hexsha) + repo.create_tag("2.0.3", message="Bug fix release") + + ## Backport fixes to release-1.x branch + fix_commit = release_2_x_branch.commit + repo.git.checkout(release_1_x_branch) + repo.git.cherry_pick(fix_commit) + repo.create_tag("1.2.4", message="Bug fix release") + + return repo + + +def create_new_file(repo_path: str, file_path: str, file_content: str): + """Create a new file in the repository.""" + + file_name = os.path.join(repo_path, file_path) + with open(file_name, "a") as f: + f.write(file_content) + + return file_name + + +def add_file(repo_path: str, src_file_path: str, dst_file_path: str | None = None): + """Adds a file in the repository path.""" + + if not dst_file_path: + dst_file_path = src_file_path + + dst_path = os.path.join(repo_path, dst_file_path) + src_path = os.path.join("scripts/release/testdata", src_file_path) + + return shutil.copy(src_path, dst_path) diff --git a/scripts/release/release_notes.py b/scripts/release/release_notes.py new file mode 100644 index 000000000..30191eedd --- /dev/null +++ b/scripts/release/release_notes.py @@ -0,0 +1,109 @@ +import argparse +import pathlib + +from git import Repo +from jinja2 import Template + +from scripts.release.changelog import CHANGELOG_PATH, ChangeType, get_changelog_entries +from scripts.release.version import ( + calculate_next_release_version, + find_previous_version, +) + + +def generate_release_notes( + repository_path: str = ".", + changelog_sub_path: str = CHANGELOG_PATH, + initial_commit_sha: str = None, + initial_version: str = "1.0.0", +) -> str: + """Generate a release notes based on the changes since the previous version tag. + + Parameters: + repository_path: Path to the Git repository. Default is the current directory '.'. + changelog_sub_path: Path to the changelog directory relative to the repository root. Default is 'changelog/'. + initial_commit_sha: SHA of the initial commit to start from if no previous version tag is found. + initial_version: Version to use if no previous version tag is found. Default is "1.0.0". + + Returns: + Formatted release notes as a string. + """ + repo = Repo(repository_path) + + version, changelog = calculate_next_version_with_changelog( + repo, changelog_sub_path, initial_commit_sha, initial_version + ) + + with open("scripts/release/release_notes_tpl.md", "r") as f: + template = Template(f.read()) + + parameters = { + "version": version, + "preludes": [c[1] for c in changelog if c[0] == ChangeType.PRELUDE], + "breaking_changes": [c[1] for c in changelog if c[0] == ChangeType.BREAKING], + "features": [c[1] for c in changelog if c[0] == ChangeType.FEATURE], + "fixes": [c[1] for c in changelog if c[0] == ChangeType.FIX], + "others": [c[1] for c in changelog if c[0] == ChangeType.OTHER], + } + + return template.render(parameters) + + +def calculate_next_version_with_changelog( + repo: Repo, changelog_sub_path: str, initial_commit_sha: str | None, initial_version: str +) -> (str, list[tuple[ChangeType, str]]): + previous_version_tag, previous_version_commit = find_previous_version(repo, initial_commit_sha) + + changelog: list[tuple[ChangeType, str]] = get_changelog_entries(previous_version_commit, repo, changelog_sub_path) + changelog_types = list[ChangeType](map(lambda x: x[0], changelog)) + + # If there is no previous version tag, we start with the initial version tag + if not previous_version_tag: + version = initial_version + else: + version = calculate_next_release_version(previous_version_tag.name, changelog_types) + + return version, changelog + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--path", + action="store", + default=".", + type=pathlib.Path, + help="Path to the Git repository. Default is the current directory '.'", + ) + parser.add_argument( + "--changelog_path", + default="changelog/", + action="store", + type=str, + help="Path to the changelog directory relative to the repository root. Default is 'changelog/'", + ) + parser.add_argument( + "--initial_commit_sha", + action="store", + type=str, + help="SHA of the initial commit to start from if no previous version tag is found.", + ) + parser.add_argument( + "--initial_version", + default="1.0.0", + action="store", + type=str, + help="Version to use if no previous version tag is found. Default is '1.0.0'", + ) + parser.add_argument("--output", "-o", type=pathlib.Path) + args = parser.parse_args() + + release_notes = generate_release_notes( + args.path, args.changelog_path, args.initial_commit_sha, args.initial_version + ) + + if args.output is not None: + with open(args.output, "w") as file: + file.write(release_notes) + else: + print(release_notes) diff --git a/scripts/release/release_notes_test.py b/scripts/release/release_notes_test.py new file mode 100644 index 000000000..aea8d5e4b --- /dev/null +++ b/scripts/release/release_notes_test.py @@ -0,0 +1,71 @@ +from conftest import git_repo +from git import Repo + +from scripts.release.release_notes import generate_release_notes + + +def test_generate_release_notes_before_1_0_0(git_repo: Repo): + initial_commit = list(git_repo.iter_commits(reverse=True))[0] + git_repo.git.checkout(initial_commit) + release_notes = generate_release_notes(git_repo.working_dir) + with open("scripts/release/testdata/release_notes_1.0.0_empty.md") as file: + assert release_notes == file.read() + + +def test_generate_release_notes_1_0_0(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.0.0") + + +def test_generate_release_notes_1_0_1(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.0.1") + + +def test_generate_release_notes_1_1_0(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.1.0") + + +def test_generate_release_notes_1_2_0(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.2.0") + + +def test_generate_release_notes_2_0_0(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "2.0.0") + + +def test_generate_release_notes_1_2_1(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.2.1") + + +def test_generate_release_notes_2_0_1(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "2.0.1") + + +def test_generate_release_notes_1_2_2(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.2.2") + + +def test_generate_release_notes_2_0_2(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "2.0.2") + + +def test_generate_release_notes_1_2_3(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.2.3") + + +def test_generate_release_notes_3_0_0(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "3.0.0") + + +def test_generate_release_notes_2_0_3(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "2.0.3") + + +def test_generate_release_notes_1_2_4(git_repo: Repo): + checkout_and_assert_release_notes(git_repo, "1.2.4") + + +def checkout_and_assert_release_notes(git_repo: Repo, tag: str): + git_repo.git.checkout(tag) + release_notes = generate_release_notes(git_repo.working_dir) + with open(f"scripts/release/testdata/release_notes_{tag}.md") as file: + assert release_notes == file.read() diff --git a/scripts/release/release_notes_tpl.md b/scripts/release/release_notes_tpl.md new file mode 100644 index 000000000..98fa260de --- /dev/null +++ b/scripts/release/release_notes_tpl.md @@ -0,0 +1,34 @@ +# MCK {{ version }} Release Notes +{% if preludes %} +{% for prelude in preludes -%} +{{- prelude }} +{%- endfor -%} +{%- endif -%} +{% if breaking_changes %} +## Breaking Changes + +{% for change in breaking_changes -%} +{{- change -}} +{%- endfor -%} +{%- endif -%} +{% if features %} +## New Features + +{% for feature in features -%} +{{- feature -}} +{%- endfor -%} +{%- endif -%} +{% if fixes %} +## Bug Fixes + +{% for fix in fixes -%} +{{- fix -}} +{%- endfor -%} +{%- endif -%} +{% if other %} +## Other Changes + +{% for other in others -%} +{{- other -}} +{%- endfor -%} +{%- endif -%} diff --git a/scripts/release/test_git_repo.mmd b/scripts/release/test_git_repo.mmd new file mode 100644 index 000000000..dad684d00 --- /dev/null +++ b/scripts/release/test_git_repo.mmd @@ -0,0 +1,45 @@ +%%{ + init: { + 'logLevel': 'debug', + 'theme': 'dark', + 'gitGraph': { + 'showBranches': true, + 'mainBranchName': 'master', + 'parallelCommits': 'true' + } + } +}%% +gitGraph + commit id: "initial commit" + commit id: "release notes prelude MCK" tag: "1.0.0" + commit id: "olm missing images fix" + commit id: "fix watched list in helm" tag: "1.0.1" + commit id: "private search preview" + commit id: "add limitations in changelog for private search preview" tag: "1.1.0" + commit id: "OIDC integration" tag: "1.2.0" + branch release-1.x + commit id: "Static architecture as default" + commit id: "Ops Manager no service mesh support" + commit id: "Fixes for static architecture" + commit id: "Release notes prelude for static architecture" tag: "2.0.0" + checkout release-1.x + commit id: "Cherry-pick: Fixes for static architecture" tag: "1.2.1" + checkout master + commit id: "placeholder fix" + commit id: "fix clusterspeclist validation" tag: "2.0.1" + checkout release-1.x + commit id: "Cherry-pick: placeholder fix" + commit id: "Cherry-pick: fix clusterspeclist validation" tag: "1.2.2" + checkout master + commit id: "fix proxy env var validation" tag: "2.0.2" + branch release-2.x + checkout release-1.x + commit id: "Cherry-pick: fix proxy env var validation" tag: "1.2.3" + checkout master + commit id: "Moved MongoDBMulti into single MongoDB resource" + commit id: "Public search support" + commit id: "MongoDBUser phase update fix" tag: "3.0.0" + checkout release-2.x + commit id: "cherry-pick from master: MongoDBUser phase update fix" tag: "2.0.3" + checkout release-1.x + commit id: "cherry-pick from release-2.x: MongoDBUser phase update fix" tag: "1.2.4" diff --git a/scripts/release/testdata/changelog/20250506_prelude_mck.md b/scripts/release/testdata/changelog/20250506_prelude_mck.md new file mode 100644 index 000000000..209c99008 --- /dev/null +++ b/scripts/release/testdata/changelog/20250506_prelude_mck.md @@ -0,0 +1,34 @@ +Exciting news for MongoDB on Kubernetes\! We're happy to announce the first release of MongoDB Controllers for Kubernetes (MCK), a unified open-source operator merging our support of MongoDB Community and Enterprise in Kubernetes. + +**Acronyms** + +* **MCK:** MongoDB Controllers for Kubernetes +* **MCO:** MongoDB Community Operator +* **MEKO:** MongoDB Enterprise Kubernetes Operator + +**TL;DR:** + +* MCK: A unified MongoDB Kubernetes Operator, merging MCO and MEKO. +* This initial release provides the combined functionality of the latest MCO and MEKO so migration is seamless: no changes are required in your current deployments. +* No impact on current contracts or agreements. +* We are adopting Semantic Versioning (SemVer), so any future breaking changes will only occur in new major versions of the Operator. +* MCO End-of-Life (EOL): Support for MCO is best efforts, with no formal EOL for each version. For the last version of MCO, we will continue to offer best efforts guidance, but there will be no further releases. +* MEKO End-of-Life (EOL): No change to the [current EOL](https://www.mongodb.com/docs/kubernetes-operator/current/reference/support-lifecycle/) for each individual MEKO version. + +**About the First MCK Release** + +MongoDB is unifying its Kubernetes offerings with the introduction of MongoDB Controllers for Kubernetes (MCK). This new operator is an open-source project and represents a merge of the previous MongoDB Community Operator (MCO) and the MongoDB Enterprise Kubernetes Operator (MEKO). + +This release brings MongoDB Community and Enterprise editions together under a single, unified operator, making it easier to manage, scale, and upgrade your deployments. While the first version simply brings the capabilities of both into a single Operator, future changes will build on this to more closely align how Community and Enterprise are managed in Kubernetes, to offer an even more seamless and streamlined experience. As an open-source project, it now allows for community contributions, helping drive quicker bug fixes and ongoing innovation. + +**License** + +Customers with contracts that allowed use of the Enterprise Operator will still be able to leverage the new replacement, allowing customers to adopt it without contract changes. The Operator itself is licensed under the Apache 2.0, and a license file [included in the repository](#) provides further detail. License entitlements for all other MongoDB products and tools remain unchanged (for example Enterprise Server and Ops Manager) \- if in doubt, contact your MongoDB account team. + +**Migration** + +Migration from MCO and MEKO to MCK is seamless: your MongoDB deployments are not impacted by the upgrade and require no changes. Simply follow the upgrade instructions provided in the MCK documentation. See our [migration guidance](https://www.mongodb.com/docs/kubernetes/current/tutorial/migrate-to-mck/). + +**Deprecation and EOL for MCO and MEKO** + +We will continue best efforts support of MCO for 6 months (until November, 2025), and versions of MEKO will remain supported according to the current [current EOL](https://www.mongodb.com/docs/kubernetes-operator/current/reference/support-lifecycle/) guidance. All future bug fixes and improvements will be released in new versions of MCK. We encourage all users to plan their migration to MCK within these timelines. diff --git a/scripts/release/testdata/changelog/20250510_fix_olm_missing_images.md b/scripts/release/testdata/changelog/20250510_fix_olm_missing_images.md new file mode 100644 index 000000000..e520dcf36 --- /dev/null +++ b/scripts/release/testdata/changelog/20250510_fix_olm_missing_images.md @@ -0,0 +1 @@ +* Fix missing agent images in the operator bundle in OpenShift catalog and operatorhub.io. diff --git a/scripts/release/testdata/changelog/20250510_fix_watched_list_in_helm.md b/scripts/release/testdata/changelog/20250510_fix_watched_list_in_helm.md new file mode 100644 index 000000000..42c05bfc2 --- /dev/null +++ b/scripts/release/testdata/changelog/20250510_fix_watched_list_in_helm.md @@ -0,0 +1 @@ +* **MongoDBCommunity** resource was missing from watched list in Helm Charts diff --git a/scripts/release/testdata/changelog/20250523_feature_community_search_preview.md b/scripts/release/testdata/changelog/20250523_feature_community_search_preview.md new file mode 100644 index 000000000..b51af5d76 --- /dev/null +++ b/scripts/release/testdata/changelog/20250523_feature_community_search_preview.md @@ -0,0 +1,3 @@ +* **MongoDBSearch (Community Private Preview)**: Added support for deploying MongoDB Search (Community Private Preview Edition) that enables full-text and vector search capabilities for MongoDBCommunity deployments. + * Added new MongoDB CRD which is watched by default by the operator. + * For more information please see: [docs/community-search/quick-start/README.md](docs/community-search/quick-start/README.md) diff --git a/scripts/release/testdata/changelog/20250523_feature_community_search_preview_UPDATED.md b/scripts/release/testdata/changelog/20250523_feature_community_search_preview_UPDATED.md new file mode 100644 index 000000000..7aa2269d4 --- /dev/null +++ b/scripts/release/testdata/changelog/20250523_feature_community_search_preview_UPDATED.md @@ -0,0 +1,6 @@ +* **MongoDBSearch (Community Private Preview)**: Added support for deploying MongoDB Search (Community Private Preview Edition) that enables full-text and vector search capabilities for MongoDBCommunity deployments. + * Added new MongoDB CRD which is watched by default by the operator. + * For more information please see: [docs/community-search/quick-start/README.md](docs/community-search/quick-start/README.md) + * Private Preview phase comes with some limitations: + * minimum MongoDB Community version: 8.0. + * TLS must be disabled in MongoDB (communication between mongot and mongod is in plaintext for now). diff --git a/scripts/release/testdata/changelog/20250610_feature_oidc.md b/scripts/release/testdata/changelog/20250610_feature_oidc.md new file mode 100644 index 000000000..2aedae72e --- /dev/null +++ b/scripts/release/testdata/changelog/20250610_feature_oidc.md @@ -0,0 +1,9 @@ +* **MongoDB**, **MongoDBMulti**: Added support for OpenID Connect (OIDC) user authentication. + * OIDC authentication can be configured with `spec.security.authentication.modes=OIDC` and `spec.security.authentication.oidcProviderConfigs` settings. + * Minimum MongoDB version requirements: + * `7.0.11`, `8.0.0` + * Only supported with MongoDB Enterprise Server + * For more information please see: + * [Secure Client Authentication with OIDC](https://www.mongodb.com/docs/kubernetes/upcoming/tutorial/secure-client-connections/) + * [Manage Database Users using OIDC](https://www.mongodb.com/docs/kubernetes/upcoming/manage-users/) + * [Authentication and Authorization with OIDC/OAuth 2.0](https://www.mongodb.com/docs/manual/core/oidc/security-oidc/) diff --git a/scripts/release/testdata/changelog/20250612_breaking_static_as_default.md b/scripts/release/testdata/changelog/20250612_breaking_static_as_default.md new file mode 100644 index 000000000..ed2bb7775 --- /dev/null +++ b/scripts/release/testdata/changelog/20250612_breaking_static_as_default.md @@ -0,0 +1 @@ +* **MongoDB**, **MongoDBMulti**: Static architecture is now the default for MongoDB and MongoDBMulti resources. diff --git a/scripts/release/testdata/changelog/20250616_feature_om_no_service_mesh.md b/scripts/release/testdata/changelog/20250616_feature_om_no_service_mesh.md new file mode 100644 index 000000000..ff96ff558 --- /dev/null +++ b/scripts/release/testdata/changelog/20250616_feature_om_no_service_mesh.md @@ -0,0 +1,7 @@ +* **MongoDBOpsManager**, **AppDB**: Introduced support for OpsManager and Application Database deployments across multiple Kubernetes clusters without requiring a Service Mesh. + * New property [spec.applicationDatabase.externalAccess](TBD) used for common service configuration or in single cluster deployments + * Added support for existing, but unused property [spec.applicationDatabase.clusterSpecList.externalAccess](TBD) + * You can define annotations for external services managed by the operator that contain placeholders which will be automatically replaced to the proper values: + * AppDB: [spec.applicationDatabase.externalAccess.externalService.annotations](TBD) + * MongoDBOpsManager: Due to different way of configuring external service placeholders are not yet supported + * More details can be found in the [public documentation](TBD). diff --git a/scripts/release/testdata/changelog/20250620_fix_static_container.md b/scripts/release/testdata/changelog/20250620_fix_static_container.md new file mode 100644 index 000000000..d9071c434 --- /dev/null +++ b/scripts/release/testdata/changelog/20250620_fix_static_container.md @@ -0,0 +1 @@ +* Fixed a bug where workloads in the `static` container architecture were still downloading binaries. This occurred when the operator was running with the default container architecture set to `non-static`, but the workload was deployed with the `static` architecture using the `mongodb.com/v1.architecture: "static"` annotation. diff --git a/scripts/release/testdata/changelog/20250622_fix_external_access.md b/scripts/release/testdata/changelog/20250622_fix_external_access.md new file mode 100644 index 000000000..01f417c8f --- /dev/null +++ b/scripts/release/testdata/changelog/20250622_fix_external_access.md @@ -0,0 +1 @@ +* **MongoDB**: Operator now correctly applies the external service customization based on `spec.externalAccess` and `spec.mongos.clusterSpecList.externalAccess` configuration. Previously it was ignored, but only for Multi Cluster Sharded Clusters. diff --git a/scripts/release/testdata/changelog/20250623_prelude_static.md b/scripts/release/testdata/changelog/20250623_prelude_static.md new file mode 100644 index 000000000..018fde24f --- /dev/null +++ b/scripts/release/testdata/changelog/20250623_prelude_static.md @@ -0,0 +1 @@ +This change is making `static` architecture a default and deprecates the `non-static` architecture. diff --git a/scripts/release/testdata/changelog/20250701_fix_placeholder.md b/scripts/release/testdata/changelog/20250701_fix_placeholder.md new file mode 100644 index 000000000..ede8e1b5d --- /dev/null +++ b/scripts/release/testdata/changelog/20250701_fix_placeholder.md @@ -0,0 +1 @@ +* **MongoDB**: Fixed placeholder name for `mongos` in Single Cluster Sharded with External Domain set. Previously it was called `mongodProcessDomain` and `mongodProcessFQDN` now they're called `mongosProcessDomain` and `mongosProcessFQDN`. diff --git a/scripts/release/testdata/changelog/20250702_fix_clusterspeclist_validation.md b/scripts/release/testdata/changelog/20250702_fix_clusterspeclist_validation.md new file mode 100644 index 000000000..17a5cf292 --- /dev/null +++ b/scripts/release/testdata/changelog/20250702_fix_clusterspeclist_validation.md @@ -0,0 +1 @@ +* **MongoDB**, **MongoDBMultiCluster**, **MongoDBOpsManager**: In case of losing one of the member clusters we no longer emit validation errors if the failed cluster still exists in the `clusterSpecList`. This allows easier reconfiguration of the deployments as part of disaster recovery procedure. diff --git a/scripts/release/testdata/changelog/20250707_fix_proxy_env_var.md b/scripts/release/testdata/changelog/20250707_fix_proxy_env_var.md new file mode 100644 index 000000000..d33f831f9 --- /dev/null +++ b/scripts/release/testdata/changelog/20250707_fix_proxy_env_var.md @@ -0,0 +1 @@ +* Fixed handling proxy environment variables in the operator pod. The environment variables [`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`] when set on the operator pod, can now be propagated to the MongoDB agents by also setting the environment variable `MDB_PROPAGATE_PROXY_ENV=true`. diff --git a/scripts/release/testdata/changelog/20250710_breaking_mongodbmulti_refactor.md b/scripts/release/testdata/changelog/20250710_breaking_mongodbmulti_refactor.md new file mode 100644 index 000000000..d1ae671a0 --- /dev/null +++ b/scripts/release/testdata/changelog/20250710_breaking_mongodbmulti_refactor.md @@ -0,0 +1 @@ +* **MongoDB**, **MongoDBMulti**: Combined both resources into single **MongoDB** resource. diff --git a/scripts/release/testdata/changelog/20250710_prelude_mongodbmulti_refactor.md b/scripts/release/testdata/changelog/20250710_prelude_mongodbmulti_refactor.md new file mode 100644 index 000000000..ee74b14c7 --- /dev/null +++ b/scripts/release/testdata/changelog/20250710_prelude_mongodbmulti_refactor.md @@ -0,0 +1 @@ +This is a new major release of the MongoDB Kubernetes Operator (MCK) with significant changes and improvements. diff --git a/scripts/release/testdata/changelog/20250711_feature_public_search.md b/scripts/release/testdata/changelog/20250711_feature_public_search.md new file mode 100644 index 000000000..8c4b824f1 --- /dev/null +++ b/scripts/release/testdata/changelog/20250711_feature_public_search.md @@ -0,0 +1,2 @@ +* **MongoDB**: public search preview release of MongoDB Search (Community Edition) is now available. + * Added new property [spec.search](https://www.mongodb.com/docs/kubernetes/current/mongodb/specification/#spec-search) to enable MongoDB Search. diff --git a/scripts/release/testdata/changelog/20250712_fix_mongodbuser_phase.md b/scripts/release/testdata/changelog/20250712_fix_mongodbuser_phase.md new file mode 100644 index 000000000..ca2cf71bd --- /dev/null +++ b/scripts/release/testdata/changelog/20250712_fix_mongodbuser_phase.md @@ -0,0 +1 @@ +* Fixes the bug when status of `MongoDBUser` was being set to `Updated` prematurely. For example, new users were not immediately usable following `MongoDBUser` creation despite the operator reporting `Updated` state. diff --git a/scripts/release/testdata/release_notes_1.0.0.md b/scripts/release/testdata/release_notes_1.0.0.md new file mode 100644 index 000000000..b7523d179 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.0.0.md @@ -0,0 +1,36 @@ +# MCK 1.0.0 Release Notes + +Exciting news for MongoDB on Kubernetes\! We're happy to announce the first release of MongoDB Controllers for Kubernetes (MCK), a unified open-source operator merging our support of MongoDB Community and Enterprise in Kubernetes. + +**Acronyms** + +* **MCK:** MongoDB Controllers for Kubernetes +* **MCO:** MongoDB Community Operator +* **MEKO:** MongoDB Enterprise Kubernetes Operator + +**TL;DR:** + +* MCK: A unified MongoDB Kubernetes Operator, merging MCO and MEKO. +* This initial release provides the combined functionality of the latest MCO and MEKO so migration is seamless: no changes are required in your current deployments. +* No impact on current contracts or agreements. +* We are adopting Semantic Versioning (SemVer), so any future breaking changes will only occur in new major versions of the Operator. +* MCO End-of-Life (EOL): Support for MCO is best efforts, with no formal EOL for each version. For the last version of MCO, we will continue to offer best efforts guidance, but there will be no further releases. +* MEKO End-of-Life (EOL): No change to the [current EOL](https://www.mongodb.com/docs/kubernetes-operator/current/reference/support-lifecycle/) for each individual MEKO version. + +**About the First MCK Release** + +MongoDB is unifying its Kubernetes offerings with the introduction of MongoDB Controllers for Kubernetes (MCK). This new operator is an open-source project and represents a merge of the previous MongoDB Community Operator (MCO) and the MongoDB Enterprise Kubernetes Operator (MEKO). + +This release brings MongoDB Community and Enterprise editions together under a single, unified operator, making it easier to manage, scale, and upgrade your deployments. While the first version simply brings the capabilities of both into a single Operator, future changes will build on this to more closely align how Community and Enterprise are managed in Kubernetes, to offer an even more seamless and streamlined experience. As an open-source project, it now allows for community contributions, helping drive quicker bug fixes and ongoing innovation. + +**License** + +Customers with contracts that allowed use of the Enterprise Operator will still be able to leverage the new replacement, allowing customers to adopt it without contract changes. The Operator itself is licensed under the Apache 2.0, and a license file [included in the repository](#) provides further detail. License entitlements for all other MongoDB products and tools remain unchanged (for example Enterprise Server and Ops Manager) \- if in doubt, contact your MongoDB account team. + +**Migration** + +Migration from MCO and MEKO to MCK is seamless: your MongoDB deployments are not impacted by the upgrade and require no changes. Simply follow the upgrade instructions provided in the MCK documentation. See our [migration guidance](https://www.mongodb.com/docs/kubernetes/current/tutorial/migrate-to-mck/). + +**Deprecation and EOL for MCO and MEKO** + +We will continue best efforts support of MCO for 6 months (until November, 2025), and versions of MEKO will remain supported according to the current [current EOL](https://www.mongodb.com/docs/kubernetes-operator/current/reference/support-lifecycle/) guidance. All future bug fixes and improvements will be released in new versions of MCK. We encourage all users to plan their migration to MCK within these timelines. diff --git a/scripts/release/testdata/release_notes_1.0.0_empty.md b/scripts/release/testdata/release_notes_1.0.0_empty.md new file mode 100644 index 000000000..14ce7e214 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.0.0_empty.md @@ -0,0 +1 @@ +# MCK 1.0.0 Release Notes diff --git a/scripts/release/testdata/release_notes_1.0.1.md b/scripts/release/testdata/release_notes_1.0.1.md new file mode 100644 index 000000000..18e59fc25 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.0.1.md @@ -0,0 +1,6 @@ +# MCK 1.0.1 Release Notes + +## Bug Fixes + +* Fix missing agent images in the operator bundle in OpenShift catalog and operatorhub.io. +* **MongoDBCommunity** resource was missing from watched list in Helm Charts diff --git a/scripts/release/testdata/release_notes_1.1.0.md b/scripts/release/testdata/release_notes_1.1.0.md new file mode 100644 index 000000000..d6d630cae --- /dev/null +++ b/scripts/release/testdata/release_notes_1.1.0.md @@ -0,0 +1,10 @@ +# MCK 1.1.0 Release Notes + +## New Features + +* **MongoDBSearch (Community Private Preview)**: Added support for deploying MongoDB Search (Community Private Preview Edition) that enables full-text and vector search capabilities for MongoDBCommunity deployments. + * Added new MongoDB CRD which is watched by default by the operator. + * For more information please see: [docs/community-search/quick-start/README.md](docs/community-search/quick-start/README.md) + * Private Preview phase comes with some limitations: + * minimum MongoDB Community version: 8.0. + * TLS must be disabled in MongoDB (communication between mongot and mongod is in plaintext for now). diff --git a/scripts/release/testdata/release_notes_1.2.0.md b/scripts/release/testdata/release_notes_1.2.0.md new file mode 100644 index 000000000..ba0ef4416 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.2.0.md @@ -0,0 +1,13 @@ +# MCK 1.2.0 Release Notes + +## New Features + +* **MongoDB**, **MongoDBMulti**: Added support for OpenID Connect (OIDC) user authentication. + * OIDC authentication can be configured with `spec.security.authentication.modes=OIDC` and `spec.security.authentication.oidcProviderConfigs` settings. + * Minimum MongoDB version requirements: + * `7.0.11`, `8.0.0` + * Only supported with MongoDB Enterprise Server + * For more information please see: + * [Secure Client Authentication with OIDC](https://www.mongodb.com/docs/kubernetes/upcoming/tutorial/secure-client-connections/) + * [Manage Database Users using OIDC](https://www.mongodb.com/docs/kubernetes/upcoming/manage-users/) + * [Authentication and Authorization with OIDC/OAuth 2.0](https://www.mongodb.com/docs/manual/core/oidc/security-oidc/) diff --git a/scripts/release/testdata/release_notes_1.2.1.md b/scripts/release/testdata/release_notes_1.2.1.md new file mode 100644 index 000000000..3a661f22f --- /dev/null +++ b/scripts/release/testdata/release_notes_1.2.1.md @@ -0,0 +1,6 @@ +# MCK 1.2.1 Release Notes + +## Bug Fixes + +* Fixed a bug where workloads in the `static` container architecture were still downloading binaries. This occurred when the operator was running with the default container architecture set to `non-static`, but the workload was deployed with the `static` architecture using the `mongodb.com/v1.architecture: "static"` annotation. +* **MongoDB**: Operator now correctly applies the external service customization based on `spec.externalAccess` and `spec.mongos.clusterSpecList.externalAccess` configuration. Previously it was ignored, but only for Multi Cluster Sharded Clusters. diff --git a/scripts/release/testdata/release_notes_1.2.2.md b/scripts/release/testdata/release_notes_1.2.2.md new file mode 100644 index 000000000..647bd5d06 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.2.2.md @@ -0,0 +1,6 @@ +# MCK 1.2.2 Release Notes + +## Bug Fixes + +* **MongoDB**: Fixed placeholder name for `mongos` in Single Cluster Sharded with External Domain set. Previously it was called `mongodProcessDomain` and `mongodProcessFQDN` now they're called `mongosProcessDomain` and `mongosProcessFQDN`. +* **MongoDB**, **MongoDBMultiCluster**, **MongoDBOpsManager**: In case of losing one of the member clusters we no longer emit validation errors if the failed cluster still exists in the `clusterSpecList`. This allows easier reconfiguration of the deployments as part of disaster recovery procedure. diff --git a/scripts/release/testdata/release_notes_1.2.3.md b/scripts/release/testdata/release_notes_1.2.3.md new file mode 100644 index 000000000..b16648823 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.2.3.md @@ -0,0 +1,5 @@ +# MCK 1.2.3 Release Notes + +## Bug Fixes + +* Fixed handling proxy environment variables in the operator pod. The environment variables [`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`] when set on the operator pod, can now be propagated to the MongoDB agents by also setting the environment variable `MDB_PROPAGATE_PROXY_ENV=true`. diff --git a/scripts/release/testdata/release_notes_1.2.4.md b/scripts/release/testdata/release_notes_1.2.4.md new file mode 100644 index 000000000..deb6121d0 --- /dev/null +++ b/scripts/release/testdata/release_notes_1.2.4.md @@ -0,0 +1,5 @@ +# MCK 1.2.4 Release Notes + +## Bug Fixes + +* Fixes the bug when status of `MongoDBUser` was being set to `Updated` prematurely. For example, new users were not immediately usable following `MongoDBUser` creation despite the operator reporting `Updated` state. diff --git a/scripts/release/testdata/release_notes_2.0.0.md b/scripts/release/testdata/release_notes_2.0.0.md new file mode 100644 index 000000000..20057efd0 --- /dev/null +++ b/scripts/release/testdata/release_notes_2.0.0.md @@ -0,0 +1,22 @@ +# MCK 2.0.0 Release Notes + +This change is making `static` architecture a default and deprecates the `non-static` architecture. + +## Breaking Changes + +* **MongoDB**, **MongoDBMulti**: Static architecture is now the default for MongoDB and MongoDBMulti resources. + +## New Features + +* **MongoDBOpsManager**, **AppDB**: Introduced support for OpsManager and Application Database deployments across multiple Kubernetes clusters without requiring a Service Mesh. + * New property [spec.applicationDatabase.externalAccess](TBD) used for common service configuration or in single cluster deployments + * Added support for existing, but unused property [spec.applicationDatabase.clusterSpecList.externalAccess](TBD) + * You can define annotations for external services managed by the operator that contain placeholders which will be automatically replaced to the proper values: + * AppDB: [spec.applicationDatabase.externalAccess.externalService.annotations](TBD) + * MongoDBOpsManager: Due to different way of configuring external service placeholders are not yet supported + * More details can be found in the [public documentation](TBD). + +## Bug Fixes + +* Fixed a bug where workloads in the `static` container architecture were still downloading binaries. This occurred when the operator was running with the default container architecture set to `non-static`, but the workload was deployed with the `static` architecture using the `mongodb.com/v1.architecture: "static"` annotation. +* **MongoDB**: Operator now correctly applies the external service customization based on `spec.externalAccess` and `spec.mongos.clusterSpecList.externalAccess` configuration. Previously it was ignored, but only for Multi Cluster Sharded Clusters. diff --git a/scripts/release/testdata/release_notes_2.0.1.md b/scripts/release/testdata/release_notes_2.0.1.md new file mode 100644 index 000000000..7310f81e6 --- /dev/null +++ b/scripts/release/testdata/release_notes_2.0.1.md @@ -0,0 +1,6 @@ +# MCK 2.0.1 Release Notes + +## Bug Fixes + +* **MongoDB**: Fixed placeholder name for `mongos` in Single Cluster Sharded with External Domain set. Previously it was called `mongodProcessDomain` and `mongodProcessFQDN` now they're called `mongosProcessDomain` and `mongosProcessFQDN`. +* **MongoDB**, **MongoDBMultiCluster**, **MongoDBOpsManager**: In case of losing one of the member clusters we no longer emit validation errors if the failed cluster still exists in the `clusterSpecList`. This allows easier reconfiguration of the deployments as part of disaster recovery procedure. diff --git a/scripts/release/testdata/release_notes_2.0.2.md b/scripts/release/testdata/release_notes_2.0.2.md new file mode 100644 index 000000000..852b79839 --- /dev/null +++ b/scripts/release/testdata/release_notes_2.0.2.md @@ -0,0 +1,5 @@ +# MCK 2.0.2 Release Notes + +## Bug Fixes + +* Fixed handling proxy environment variables in the operator pod. The environment variables [`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`] when set on the operator pod, can now be propagated to the MongoDB agents by also setting the environment variable `MDB_PROPAGATE_PROXY_ENV=true`. diff --git a/scripts/release/testdata/release_notes_2.0.3.md b/scripts/release/testdata/release_notes_2.0.3.md new file mode 100644 index 000000000..7772ce1cf --- /dev/null +++ b/scripts/release/testdata/release_notes_2.0.3.md @@ -0,0 +1,5 @@ +# MCK 2.0.3 Release Notes + +## Bug Fixes + +* Fixes the bug when status of `MongoDBUser` was being set to `Updated` prematurely. For example, new users were not immediately usable following `MongoDBUser` creation despite the operator reporting `Updated` state. diff --git a/scripts/release/testdata/release_notes_3.0.0.md b/scripts/release/testdata/release_notes_3.0.0.md new file mode 100644 index 000000000..a74f2b491 --- /dev/null +++ b/scripts/release/testdata/release_notes_3.0.0.md @@ -0,0 +1,16 @@ +# MCK 3.0.0 Release Notes + +This is a new major release of the MongoDB Kubernetes Operator (MCK) with significant changes and improvements. + +## Breaking Changes + +* **MongoDB**, **MongoDBMulti**: Combined both resources into single **MongoDB** resource. + +## New Features + +* **MongoDB**: public search preview release of MongoDB Search (Community Edition) is now available. + * Added new property [spec.search](https://www.mongodb.com/docs/kubernetes/current/mongodb/specification/#spec-search) to enable MongoDB Search. + +## Bug Fixes + +* Fixes the bug when status of `MongoDBUser` was being set to `Updated` prematurely. For example, new users were not immediately usable following `MongoDBUser` creation despite the operator reporting `Updated` state. diff --git a/scripts/release/version.py b/scripts/release/version.py new file mode 100644 index 000000000..9d1699958 --- /dev/null +++ b/scripts/release/version.py @@ -0,0 +1,50 @@ +import semver +from git import Commit, Repo, TagReference + +from scripts.release.changelog import ChangeType + + +def find_previous_version(repo: Repo, initial_commit_sha: str = None) -> (TagReference | None, Commit): + """Find the most recent version that is an ancestor of the current HEAD commit.""" + + previous_version_tag = find_previous_version_tag(repo) + + # If there is no previous version tag, we start with the initial commit + if not previous_version_tag: + # If no initial commit SHA provided, use the first commit in the repository + if not initial_commit_sha: + initial_commit_sha = list(repo.iter_commits(reverse=True))[0].hexsha + + return None, repo.commit(initial_commit_sha) + + return previous_version_tag, previous_version_tag.commit + + +def find_previous_version_tag(repo: Repo) -> TagReference | None: + """Find the most recent version tag that is an ancestor of the current HEAD commit.""" + + head_commit = repo.head.commit + + # Filter tags that are ancestors of the current HEAD commit + ancestor_tags = filter(lambda t: repo.is_ancestor(t.commit, head_commit) and t.commit != head_commit, repo.tags) + + # Filter valid SemVer tags and sort them + valid_tags = filter(lambda t: semver.VersionInfo.is_valid(t.name), ancestor_tags) + sorted_tags = sorted(valid_tags, key=lambda t: semver.VersionInfo.parse(t.name), reverse=True) + + if not sorted_tags: + return None + + return sorted_tags[0] + + +def calculate_next_release_version(previous_version_str: str, changelog: list[ChangeType]) -> str: + previous_version = semver.VersionInfo.parse(previous_version_str) + + if ChangeType.BREAKING in changelog: + return str(previous_version.bump_major()) + + if ChangeType.FEATURE in changelog: + return str(previous_version.bump_minor()) + + return str(previous_version.bump_patch()) diff --git a/scripts/release/version_test.py b/scripts/release/version_test.py new file mode 100644 index 000000000..d6ad8d4ad --- /dev/null +++ b/scripts/release/version_test.py @@ -0,0 +1,75 @@ +import unittest + +from scripts.release.changelog import ChangeType +from scripts.release.version import calculate_next_release_version + + +class TestCalculateNextReleaseVersion(unittest.TestCase): + + def test_bump_major_version(self): + previous_version = "1.2.3" + changelog = [ChangeType.BREAKING] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "2.0.0") + + def test_bump_minor_version(self): + previous_version = "1.2.3" + changelog = [ChangeType.FEATURE] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.3.0") + + def test_bump_patch_version(self): + previous_version = "1.2.3" + changelog = [ChangeType.FIX] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.2.4") + + def test_bump_patch_version_other_changes(self): + previous_version = "1.2.3" + changelog = [ChangeType.OTHER] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.2.4") + + def test_bump_patch_version_no_changes(self): + previous_version = "1.2.3" + changelog = [] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.2.4") + + def test_feature_takes_precedence(self): + # Test that FEATURE has precedence over FIX + previous_version = "1.2.3" + changelog = [ChangeType.FEATURE, ChangeType.FIX] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.3.0") + + def test_breaking_takes_precedence(self): + # Test that BREAKING has precedence over FEATURE and FIX + previous_version = "1.2.3" + changelog = [ChangeType.FEATURE, ChangeType.BREAKING, ChangeType.FIX, ChangeType.OTHER] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "2.0.0") + + def test_multiple_breaking_changes(self): + previous_version = "1.2.3" + changelog = [ChangeType.BREAKING, ChangeType.BREAKING, ChangeType.FEATURE, ChangeType.FIX, ChangeType.OTHER] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "2.0.0") + + def test_multiple_feature_changes(self): + previous_version = "1.2.3" + changelog = [ChangeType.FEATURE, ChangeType.FEATURE, ChangeType.FIX, ChangeType.OTHER] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.3.0") + + def test_multiple_fix_changes(self): + previous_version = "1.2.3" + changelog = [ChangeType.FIX, ChangeType.FIX, ChangeType.OTHER] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.2.4") + + def test_multiple_other_changes(self): + previous_version = "1.2.3" + changelog = [ChangeType.OTHER, ChangeType.OTHER] + next_version = calculate_next_release_version(previous_version, changelog) + self.assertEqual(next_version, "1.2.4")