Skip to content

Commit 986cf02

Browse files
DEVOPS-68 automatically create jira issues based on dependabot alerts
1 parent d767e83 commit 986cf02

File tree

5 files changed

+172
-44
lines changed

5 files changed

+172
-44
lines changed

common_utils.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
def limit_string_length(input_string: str, max_length=254):
2+
"""
3+
This function is created to trim the long summary and descriptions of github vumnerability.
4+
Jira is having 255 character limits for summaries. so doing for both
5+
:param input_string:
6+
:param max_length:
7+
:return:
8+
"""
9+
# Check the length of the string
10+
string_length = len(input_string)
11+
print(f"Original string length: {string_length}")
12+
13+
# Limit the string to max_length characters
14+
if string_length > max_length:
15+
input_string = input_string[:max_length]
16+
print(f"String was truncated to {max_length} characters.")
17+
18+
return input_string
19+
20+
21+
def compare_summaries_from_epic_and_scan(epic_details: list[dict], issues_list:list[dict]):
22+
"""
23+
this function is to compare the stories existing in epic with the github dependabot alerts
24+
available to not create duplicate stories. this works to ensure that duplicate wont happen
25+
-POC-
26+
:param epic_details:
27+
:param issues_list:
28+
:return:
29+
"""
30+
create_story_true = []
31+
print(epic_details)
32+
print(issues_list)
33+
for issue in issues_list:
34+
summary_from_gh = f"{issue['cve_id']} - {issue['repository']} - {issue['issue_severity']}"
35+
summary_from_gh_split = summary_from_gh.split(' - ')
36+
found_matching_story = False
37+
for story in epic_details:
38+
story_summary = story['issue_summary']
39+
story_summary_split = story_summary.split(' - ')
40+
if summary_from_gh_split[:3] == story_summary_split[:3]:
41+
print(f'Story already created for this. Story key {story["key"]}')
42+
found_matching_story = True
43+
break
44+
if not found_matching_story:
45+
create_story_true.append(issue)
46+
# create_story_true.append(issue)
47+
return create_story_true
48+
49+
50+
51+
def main():
52+
""" To test the code"""
53+
original_string = "This is a very long string..." # Replace with your actual string
54+
limited_string = limit_string_length(original_string)
55+
56+
if __name__ == "__main__":
57+
main()

date_time.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ def date_time():
55
now = datetime.now()
66
formatted_datetime = now.strftime("%d-%B-%Y %H:%M")
77
return formatted_datetime
8-
# print(formatted_datetime)
98

109
def main():
1110
""" test code """

github_scanning.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
from dotenv import load_dotenv
3-
from jira import create_story_under_epic
3+
# from jira import create_story_under_epic
44
from date_time import date_time
55
import requests
66

@@ -17,21 +17,22 @@ def scan_for_dependabot_alerts_and_issues(organization: str, repository: str):
1717
}
1818
response = requests.get(url=api_endpoint, headers=headers)
1919
response_json = response.json()
20-
print(response_json)
20+
# print(response_json)
2121
issues_list = []
2222
for issue in response_json:
2323
dict = {}
2424
dict['dependancy'] = issue['dependency']['package']
2525
dict['issue_is_at'] = issue['dependency']['manifest_path']
2626
dict['cve_id'] = issue['security_advisory']['cve_id']
27-
dict['issue_summary'] = issue['security_advisory']['summary']
27+
dict['issue_summary'] = f'{issue["security_advisory"]["summary"]} - {repository}'
2828
dict['issue_description'] = issue['security_advisory']['description']
2929
dict['issue_severity'] = issue['security_advisory']['severity']
3030
dict['issue_created_at'] = issue['created_at']
31+
dict['repository'] = repository
32+
3133

3234
# add dict to list
3335
issues_list.append(dict)
34-
print(issues_list)
3536

3637
return issues_list
3738

@@ -42,11 +43,6 @@ def main():
4243
repository = 'programatically-create-delete-update-github-repository-secrets'
4344

4445
issues_list = scan_for_dependabot_alerts_and_issues(organization=organization, repository=repository)
45-
for issues in issues_list:
46-
summary = f"{issues['cve_id']} {repository} - {issues['issue_severity']} - {issues['issue_summary']}"
47-
description = f"{issues['issue_description']} \n 'Issue Created at' - {issues['issue_created_at']}"
48-
story = create_story_under_epic(epic_key='DEVOPS-74', summary=summary, description=description)
49-
print(f"Created story {story['key']} at {date_time()} IST")
5046

5147

5248
if __name__ == "__main__":

jira.py

Lines changed: 109 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
import os
2-
import json
2+
import argparse
33
import requests
4+
from requests.auth import HTTPBasicAuth
45
from dotenv import load_dotenv
56
from atlassian import Jira
6-
7-
# Initializing jira client
8-
load_dotenv()
9-
JIRA_URL = os.getenv('JIRA_URL')
10-
JIRA_USERNAME = os.getenv('JIRA_USERNAME')
11-
JIRA_PASSWORD = os.getenv('JIRA_PASSWORD')
12-
7+
from github_scanning import scan_for_dependabot_alerts_and_issues
8+
from repos import list_repos_in_org
9+
from date_time import date_time
10+
from common_utils import limit_string_length, compare_summaries_from_epic_and_scan
1311

1412

1513
# Function to create a story under a specific epic
16-
def create_story_under_epic(epic_key, summary, description):
14+
def create_story_under_epic(epic_key: str, summary: str, description: str):
15+
"""
16+
Create jira task under a specific epic
17+
:param epic_key:
18+
:param summary:
19+
:param description:
20+
:return:
21+
"""
22+
# Initializing jira client
23+
load_dotenv()
24+
JIRA_URL = os.getenv('JIRA_URL')
25+
JIRA_USERNAME = os.getenv('JIRA_USERNAME')
26+
JIRA_PASSWORD = os.getenv('JIRA_PASSWORD')
27+
jira = Jira(url=os.getenv('JIRA_URL'), username=os.getenv('JIRA_USERNAME'), password=os.getenv('JIRA_PASSWORD'),
28+
cloud=True)
29+
# for issue in unmatched_issues:
30+
# print('h')
1731
issue_dict = {
1832
'project': {'key': 'DEVOPS'},
1933
'summary': summary,
@@ -24,48 +38,110 @@ def create_story_under_epic(epic_key, summary, description):
2438
}
2539
}
2640
new_issue = jira.issue_create(fields=issue_dict)
27-
# pic = new_issue.update(fields={'parent': {'id': epic_key}})
41+
print(f"Created story {new_issue['key']} with epic {epic_key} at {date_time()} IST")
2842

29-
return new_issue
3043

31-
32-
def issues_in_epic(epic_key: str, board_id: int):
44+
def issues_in_epic(epic_key: str):
3345
"""
3446
list available issues in a epic
3547
:param epic_key:
3648
:return:
3749
"""
50+
# Initializing jira client
51+
load_dotenv()
52+
JIRA_URL = os.getenv('JIRA_URL')
53+
JIRA_USERNAME = os.getenv('JIRA_USERNAME')
54+
JIRA_PASSWORD = os.getenv('JIRA_PASSWORD')
3855
jira = Jira(url=os.getenv('JIRA_URL'), username=os.getenv('JIRA_USERNAME'), password=os.getenv('JIRA_PASSWORD'),
3956
cloud=True)
4057

41-
# todo
58+
url = f'{JIRA_URL}/rest/api/3/search'
59+
60+
auth = HTTPBasicAuth(JIRA_USERNAME, JIRA_PASSWORD)
61+
62+
headers = {
63+
"Accept": "application/json"
64+
}
65+
66+
query = {
67+
'jql': f'Parent = {epic_key}'
68+
}
69+
response = requests.request(
70+
"GET",
71+
url,
72+
headers=headers,
73+
params=query,
74+
auth=auth
75+
)
76+
response_json = response.json()
77+
response_json_issues = response_json['issues']
78+
if not response_json_issues:
79+
issues_in_epic = []
80+
else:
81+
issues_in_epic = response_json['issues']
82+
83+
epic_details = []
84+
for issues in issues_in_epic:
85+
issue = {}
86+
issue['key'] = issues['key']
87+
issue['issue_type'] = issues['fields']['issuetype']['name']
88+
issue['issue_summary'] = issues['fields']['summary']
89+
issue['description'] = 'NA'
90+
epic_details.append(issue)
91+
92+
return epic_details
93+
94+
95+
def compare_summaries_and_create_story(repo_names: list, organization: str, epic_key: str):
96+
"""
97+
The function helps create stories by calling another function.
98+
:param issues_list:
99+
:param epic_details:
100+
:return:
101+
"""
102+
epic_details = issues_in_epic(epic_key=epic_key)
103+
104+
for repo in repo_names:
105+
# scan details
106+
issues_list = scan_for_dependabot_alerts_and_issues(organization=organization, repository=repo)
107+
if not issues_list:
108+
print(f'No dependabot security alerts found on {organization}/{repo} repository')
109+
continue
110+
create_story_true = compare_summaries_from_epic_and_scan(issues_list=issues_list, epic_details=epic_details)
111+
for issue in create_story_true:
112+
summary_from_gh = f"{issue['cve_id']} - {issue['repository']} - {issue['issue_severity']} - {issue['issue_summary']}"
113+
description_from_gh = issue['issue_description']
114+
summary_after_character_length_check = limit_string_length(input_string=summary_from_gh)
115+
description_after_character_length_check = limit_string_length(input_string=description_from_gh)
116+
117+
create_story_under_epic(summary=summary_after_character_length_check,
118+
description=description_after_character_length_check, epic_key=epic_key)
119+
42120

43-
def get_board_id(project_key: str):
44-
# jira = Jira(url=os.getenv('JIRA_URL_0'), username=os.getenv('JIRA_USERNAME'), password=os.getenv('JIRA_PASSWORD'),
45-
# cloud=True)
46-
response = requests.get(url=os.getenv('JIRA_URL_0'),auth=(os.getenv('JIRA_USERNAME'), os.getenv('JIRA_PASSWORD')))
47-
if response.status_code == 200:
48-
boards = response.json()['values']
49-
if boards:
50-
# Assuming you want the first board ID
51-
return boards[0]['id']
52-
return None
53121

54122
def main():
55123
"""testing script"""
56-
# Replace these values with your specific details
57-
epic_key = 'DEVOPS-74'
58-
summary = 'New Story Summary135'
59-
description = 'Description -- of the new story'
60124

61-
# Create the story
62-
# story = create_story_under_epic(epic_key, summary, description)
63-
# print(f"Created story {story['key']} under epic {epic_key}")
125+
parser = argparse.ArgumentParser(description='Create Jira issues prograatically based on dependabot alerts')
126+
parser.add_argument("--epic_key", help="jira epic ID", type=str, required=True)
127+
parser.add_argument('--organization', help='Github org name', type=str, required=True)
128+
129+
args = parser.parse_args()
130+
131+
epic_key = args.epic_key
132+
organization = args.organization
64133

65134
# List stories under an epic
66-
stories = issues_in_epic(epic_key=epic_key, board_id=1)
135+
epic_details = issues_in_epic(epic_key=epic_key)
136+
137+
# Repos in GitHub org
138+
repo_names = list_repos_in_org(org_name=organization)
139+
140+
# compare summaries to decide to create a new story or not
141+
compare_summaries_and_create_story(repo_names=repo_names, organization=organization,
142+
epic_key=epic_key)
143+
67144

68-
# get_board_id('DEVOPS')
69145

70146
if __name__ == "__main__":
71147
main()

repos.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
def list_repos_in_org(org_name: str):
77
"""
8-
list all repos in github organization
8+
list all repos in github organization using rest api repo end point
99
:param org_name:
1010
:return:
1111
"""

0 commit comments

Comments
 (0)