Skip to content

Commit 6c5e775

Browse files
committed
Add management command to commit exported data
Signed-off-by: Keshav Priyadarshi <[email protected]>
1 parent 433f2a9 commit 6c5e775

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import logging
11+
import os
12+
import shutil
13+
import tempfile
14+
from datetime import datetime
15+
from pathlib import Path
16+
from urllib.parse import urlparse
17+
18+
import requests
19+
from django.core.management.base import BaseCommand
20+
from django.core.management.base import CommandError
21+
from git import Repo
22+
23+
logger = logging.getLogger(__name__)
24+
25+
26+
class Command(BaseCommand):
27+
help = """Commit the exported vulnerability and package in backing git repository"""
28+
29+
def add_arguments(self, parser):
30+
parser.add_argument(
31+
"path",
32+
help="Path to exported data.",
33+
)
34+
35+
def handle(self, *args, **options):
36+
if path := options["path"]:
37+
base_path = Path(path)
38+
39+
if not path or not base_path.is_dir():
40+
raise CommandError("Enter a valid directory path")
41+
42+
export_repo_url = os.environ.get("VULNERABLECODE_EXPORT_REPO_URL", None)
43+
github_service_token = os.environ.get("GITHUB_SERVICE_TOKEN", None)
44+
github_service_name = os.environ.get("GITHUB_SERVICE_NAME", None)
45+
github_service_email = os.environ.get("GITHUB_SERVICE_EMAIL", None)
46+
47+
local_dir = tempfile.mkdtemp()
48+
current_date = datetime.now().strftime("%Y-%m-%d")
49+
50+
branch_name = f"export-update-{current_date}"
51+
commit_message = f"Update package and vulnerability data\nSigned-off-by: {github_service_name} <{github_service_email}>"
52+
pr_title = "Update package and vulnerability"
53+
pr_body = ""
54+
55+
self.stdout.write("Committing vulnerablecode Package and Vulnerability data.")
56+
repo = self.clone_repository(
57+
repo_url=export_repo_url,
58+
local_path=local_dir,
59+
token=github_service_token,
60+
)
61+
62+
repo.config_writer().set_value("user", "name", github_service_name).release()
63+
repo.config_writer().set_value("user", "email", github_service_email).release()
64+
65+
self.add_changes(repo=repo, content_path=path)
66+
67+
if self.commit_and_push_changes(
68+
repo=repo,
69+
branch=branch_name,
70+
commit_message=commit_message,
71+
):
72+
self.create_pull_request(
73+
repo_url=export_repo_url,
74+
branch=branch_name,
75+
title=pr_title,
76+
body=pr_body,
77+
token=github_service_token,
78+
)
79+
shutil.rmtree(local_dir)
80+
81+
def clone_repository(self, repo_url, local_path, token):
82+
"""Clone repository to local_path."""
83+
if os.path.exists(local_path):
84+
shutil.rmtree(local_path)
85+
86+
authenticated_repo_url = repo_url.replace("https://", f"https://{token}@")
87+
return Repo.clone_from(authenticated_repo_url, local_path)
88+
89+
def add_changes(self, repo, content_path):
90+
"""Copy changes from the ``content_path`` to ``repo``."""
91+
92+
source_path = Path(content_path)
93+
destination_path = Path(repo.working_dir)
94+
95+
for item in source_path.iterdir():
96+
if not item.is_dir():
97+
continue
98+
target_item = destination_path / item.name
99+
if target_item.exists():
100+
shutil.rmtree(target_item)
101+
shutil.copytree(item, target_item)
102+
103+
def commit_and_push_changes(self, repo, branch, commit_message, remote_name="origin"):
104+
"""Commit changes and push to remote repository, return name of changed files."""
105+
106+
repo.git.checkout("HEAD", b=branch)
107+
files_changed = repo.git.diff("HEAD", name_only=True)
108+
109+
if not files_changed:
110+
self.stderr.write(self.style.SUCCESS("No changes to commit."))
111+
return
112+
113+
repo.git.add(A=True)
114+
repo.index.commit(commit_message)
115+
repo.git.push(remote_name, branch)
116+
return files_changed
117+
118+
def create_pull_request(self, repo_url, branch, title, body, token):
119+
"""Create a pull request in the GitHub repository."""
120+
121+
url_parts = urlparse(repo_url).path
122+
path_parts = url_parts.strip("/").rstrip(".git").split("/")
123+
124+
if len(path_parts) >= 2:
125+
repo_owner = path_parts[0]
126+
repo_name = path_parts[1]
127+
else:
128+
raise ValueError("Invalid GitHub repo URL")
129+
130+
url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls"
131+
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
132+
data = {"title": title, "head": branch, "base": "main", "body": body}
133+
134+
response = requests.post(url, headers=headers, json=data)
135+
136+
if response.status_code == 201:
137+
pr_response = response.json()
138+
self.stdout.write(
139+
self.style.SUCCESS(
140+
f"Pull request created successfully: {pr_response.get('html_url')}."
141+
)
142+
)
143+
else:
144+
self.stderr.write(
145+
self.style.ERROR(f"Failed to create pull request: {response.content}")
146+
)

0 commit comments

Comments
 (0)