From 0728e2bee360a7059131a815f851600dea9cfa93 Mon Sep 17 00:00:00 2001 From: Snehanshu sahay Date: Fri, 29 Dec 2023 14:46:52 +0530 Subject: [PATCH] Add the rest call to create repo using template repo name and owner (#2148) * Add the rest call to create repo using template repo name and owner * test failures * test fix * test fix * test fix * test fix * test fix --- metecho/api/jobs.py | 141 ++++++++++++++++++++++---------------- metecho/api/tests/jobs.py | 99 +++++++++++++++++++------- 2 files changed, 157 insertions(+), 83 deletions(-) diff --git a/metecho/api/jobs.py b/metecho/api/jobs.py index 0e9f9662e..afe173067 100644 --- a/metecho/api/jobs.py +++ b/metecho/api/jobs.py @@ -27,7 +27,7 @@ from cumulusci.salesforce_api.utils import get_simple_salesforce_connection from cumulusci.tasks.github.util import CommitDir from cumulusci.tasks.vlocity.vlocity import VlocityRetrieveTask -from cumulusci.utils import download_extract_github, temporary_dir +from cumulusci.utils import temporary_dir from cumulusci.utils.http.requests_utils import safe_json_from_response from django.conf import settings from django.db import transaction @@ -241,67 +241,92 @@ def create_repository( team.add_or_update_membership(login, role=role) # Create repo on GitHub - repo = org.create_repository( - project.repo_name, description=project.description, private=False - ) - team.add_repository(repo.full_name, permission="push") - project.repo_id = repo.id + if tpl_repo: + # Calling rest api to generate repo using github template repo + gh_token = org_gh.session.auth.token + # Extract data from the request body + + # GitHub API endpoint URL for repository creation + api_url = f"https://api.github.com/repos/{tpl_repo.owner}/{tpl_repo.name}/generate" + + # Headers for the GitHub API request + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {gh_token}", # Extract GitHub token from request data + } - with temporary_dir(): - # Populate files from the template repository - if tpl_repo: - zipfile = download_extract_github(org_gh, tpl_repo.owner, tpl_repo.name) - zipfile.extractall() + # Data to be sent in the POST request to GitHub API + github_data = { + "owner": project.repo_owner, + "name": project.repo_name, + "description": project.description, + "include_all_branches": False, + "private": False, + } + # Sending a POST request to GitHub API + response = requests.post(api_url, headers=headers, json=github_data) + team.add_repository(response.json()["full_name"], permission="push") + project.repo_id = response.json()["id"] + # Checking the response status code and returning the response + if response.status_code != 201: + raise Exception("Create Repository using Template failed") - runtime = CliRuntime() + else: + repo = org.create_repository( + project.repo_name, description=project.description, private=False + ) + team.add_repository(repo.full_name, permission="push") + project.repo_id = repo.id + with temporary_dir(): - try: - # Ask the user's Dev Hub what its latest API version is - sf = get_devhub_api(devhub_username=user.sf_username) - response = requests.get( - f"https://{sf.sf_instance}/services/data", headers=sf.headers - ) + runtime = CliRuntime() + try: + # Ask the user's Dev Hub what its latest API version is + sf = get_devhub_api(devhub_username=user.sf_username) + response = requests.get( + f"https://{sf.sf_instance}/services/data", headers=sf.headers + ) - version = safe_json_from_response(response)[-1]["version"] - except Exception: - version = runtime.universal_config.project__package__api_version - - # Bootstrap repository with CumulusCI - context = { - "cci_version": cumulusci.__version__, - "project_name": project.repo_name, - "package_name": project.repo_name, - "package_namespace": None, - "api_version": version, - "source_format": "sfdx", - "dependencies": [ - {"type": "github", "url": url} for url in dependencies - ], - "git": { - "default_branch": branch_name, - "prefix_feature": "feature/", - "prefix_beta": "beta/", - "prefix_release": "release/", - }, - "test_name_match": None, - "code_coverage": 75, - } - init_from_context(context) - cmd = sarge.capture_both( - f""" - git init; - git checkout -b {branch_name}; - git config user.name '{user.get_full_name() or user.username}'; - git config user.email '{user.email}'; - git add --all; - git commit -m 'Bootstrap project (via Metecho)'; - git push https://{user_gh.session.auth.token}@github.com/{repo.full_name}.git {branch_name}; - """, # noqa: B950 - shell=True, - ) - if cmd.returncode: # non-zero return code, something's wrong - logger.error(cmd.stderr.text) - raise Exception("Failed to push files to GitHub repository") + version = safe_json_from_response(response)[-1]["version"] + except Exception: + version = runtime.universal_config.project__package__api_version + + # Bootstrap repository with CumulusCI + context = { + "cci_version": cumulusci.__version__, + "project_name": project.repo_name, + "package_name": project.repo_name, + "package_namespace": None, + "api_version": version, + "source_format": "sfdx", + "dependencies": [ + {"type": "github", "url": url} for url in dependencies + ], + "git": { + "default_branch": branch_name, + "prefix_feature": "feature/", + "prefix_beta": "beta/", + "prefix_release": "release/", + }, + "test_name_match": None, + "code_coverage": 75, + } + init_from_context(context) + cmd = sarge.capture_both( + f""" + git init; + git checkout -b {branch_name}; + git config user.name '{user.get_full_name() or user.username}'; + git config user.email '{user.email}'; + git add --all; + git commit -m 'Bootstrap project (via Metecho)'; + git push https://{user_gh.session.auth.token}@github.com/{repo.full_name}.git {branch_name}; + """, # noqa: B950 + shell=True, + ) + if cmd.returncode: # non-zero return code, something's wrong + logger.error(cmd.stderr.text) + raise Exception("Failed to push files to GitHub repository") # Copy branch protection rules from the template repo # See copy_branch_protection() for why we don't use this currently diff --git a/metecho/api/tests/jobs.py b/metecho/api/tests/jobs.py index 37a729dcc..e90924131 100644 --- a/metecho/api/tests/jobs.py +++ b/metecho/api/tests/jobs.py @@ -1378,11 +1378,13 @@ def github_mocks(self, mocker, project, git_hub_collaboration_factory): gh_user.organizations.return_value = [ mocker.MagicMock(login=project.repo_owner, spec=Organization) ] - gh_org = mocker.patch( - f"{PATCH_ROOT}.gh_as_org", autospec=True - ).return_value.organization.return_value - gh_org.create_team.return_value = team - gh_org.create_repository.return_value = repo + gh_org = mocker.patch(f"{PATCH_ROOT}.gh_as_org", autospec=True).return_value + tpl_repo = gh_org.repository.return_value + tpl_repo.owner = "Industries-SolutionFactory-Connect" + tpl_repo.name = "TemplateRepoTest" + gh_org_org = gh_org.organization.return_value + gh_org_org.create_team.return_value = team + gh_org_org.create_repository.return_value = repo get_devhub_api = mocker.patch(f"{PATCH_ROOT}.get_devhub_api", autospec=True) get_devhub_api.sf_instance = "foo" @@ -1391,7 +1393,7 @@ def github_mocks(self, mocker, project, git_hub_collaboration_factory): # Wild API version so we can easily detect it came from here requests.get.return_value.json.return_value = [{"version": "600.0"}] - return project, gh_org, team, repo, get_devhub_api, requests + return project, gh_org_org, team, repo, get_devhub_api, requests def test_ok(self, mocker, github_mocks, user_factory): user = user_factory() @@ -1400,14 +1402,13 @@ def test_ok(self, mocker, github_mocks, user_factory): sarge = mocker.patch(f"{PATCH_ROOT}.sarge", autospec=True) sarge.capture_both.return_value.returncode = 0 async_to_sync = mocker.patch("metecho.api.model_mixins.async_to_sync") - zipfile = mocker.patch(f"{PATCH_ROOT}.download_extract_github").return_value - + # zipfile = mocker.patch(f"{PATCH_ROOT}.download_extract_github").return_value create_repository( project, user=user, dependencies=["http://foo.com"], - template_repo_owner="owner", - template_repo_name="repo", + template_repo_owner=None, + template_repo_name=None, ) project.refresh_from_db() @@ -1426,26 +1427,30 @@ def test_ok(self, mocker, github_mocks, user_factory): include_user=False, ) assert sarge.capture_both.called - assert zipfile.extractall.called + # assert zipfile.extractall.called assert init_from_context.call_args_list[0][0][0]["api_version"] == "600.0" def test_ok__no_version_from_devhub(self, mocker, github_mocks, user_factory): user = user_factory() project, org, team, repo, get_devhub_api, requests = github_mocks get_devhub_api.side_effect = Exception - init_from_context = mocker.patch(f"{PATCH_ROOT}.init_from_context") + # init_from_context = mocker.patch(f"{PATCH_ROOT}.init_from_context") sarge = mocker.patch(f"{PATCH_ROOT}.sarge", autospec=True) sarge.capture_both.return_value.returncode = 0 async_to_sync = mocker.patch("metecho.api.model_mixins.async_to_sync") - zipfile = mocker.patch(f"{PATCH_ROOT}.download_extract_github").return_value - - create_repository( - project, - user=user, - dependencies=["http://foo.com"], - template_repo_owner="owner", - template_repo_name="repo", - ) + # zipfile = mocker.patch(f"{PATCH_ROOT}.download_extract_github").return_value + with patch("requests.post") as mock_post: + response = mocker.patch(f"{PATCH_ROOT}.requests.post").return_value + response.status_code = 201 + response.json.return_value = {"full_name": "value", "id": 123456} + mock_post.return_value = response + create_repository( + project, + user=user, + dependencies=["http://foo.com"], + template_repo_owner="Industries-SolutionFactory-Connect", + template_repo_name="TemplateRepoTest", + ) project.refresh_from_db() assert project.repo_id == 123456 @@ -1462,9 +1467,53 @@ def test_ok__no_version_from_devhub(self, mocker, github_mocks, user_factory): group_name=None, include_user=False, ) - assert sarge.capture_both.called - assert zipfile.extractall.called - assert init_from_context.call_args_list[0][0][0]["api_version"] != "600.0" + # assert sarge.capture_both.called + # assert zipfile.extractall.called + # assert init_from_context.call_args_list[0][0][0]["api_version"] != "600.0" + + def test_ok__exception_from_template(self, mocker, github_mocks, user_factory): + user = user_factory() + project, org, team, repo, get_devhub_api, requests = github_mocks + get_devhub_api.side_effect = Exception + sarge = mocker.patch(f"{PATCH_ROOT}.sarge", autospec=True) + sarge.capture_both.return_value.returncode = 0 + + with patch(f"{PATCH_ROOT}.Exception") as mock_exception: + mock_exception.side_effect = Exception( + "Create Repository using Template failed" + ) + + with patch("requests.post") as mock_post: + response = mocker.patch(f"{PATCH_ROOT}.requests.post").return_value + response.status_code = 404 + response.json.return_value = {"full_name": "value", "id": 123456} + mock_post.return_value = response + with pytest.raises(Exception, match="Create Repository using Template failed"): + create_repository( + project, + user=user, + dependencies=["http://foo.com"], + template_repo_owner="dummy1", + template_repo_name="dummy2", + ) + + def test_ok__exception_from_repo(self, mocker, github_mocks, user_factory): + user = user_factory() + project, org, team, repo, get_devhub_api, requests = github_mocks + get_devhub_api.side_effect = Exception + init_from_context = mocker.patch(f"{PATCH_ROOT}.init_from_context") + sarge = mocker.patch(f"{PATCH_ROOT}.sarge", autospec=True) + sarge.capture_both.return_value.returncode = 0 + + with patch(f"{PATCH_ROOT}"): + create_repository( + project, + user=user, + dependencies=["http://foo.com"], + template_repo_owner=None, + template_repo_name=None, + ) + assert init_from_context.call_args_list[0][0][0]["api_version"] != "600.0" def test__gh_error(self, mocker, caplog, project, user_factory, github_mocks): user = user_factory() @@ -1507,7 +1556,7 @@ def test__team_name_taken(self, mocker, github_mocks, project, user_factory): sarge = mocker.patch(f"{PATCH_ROOT}.sarge", autospec=True) sarge.capture_both.return_value.returncode = 0 mocker.patch("metecho.api.model_mixins.async_to_sync") - mocker.patch(f"{PATCH_ROOT}.download_extract_github").return_value + # mocker.patch(f"{PATCH_ROOT}.download_extract_github").return_value create_repository(project, user=user, dependencies=[])