Skip to content

Commit bdfbba4

Browse files
authored
Verify contribution form filled label (demisto#12617)
* added github action to verify label exists * cr fixes
1 parent d1b12dd commit bdfbba4

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Contribution Form Filled label verification
2+
on:
3+
pull_request:
4+
types: [opened, synchronize, labeled, unlabeled]
5+
6+
jobs:
7+
contribution_form_filled_verification:
8+
runs-on: ubuntu-latest
9+
if: github.repository == 'demisto/content' && github.event.pull_request.head.repo.fork == true && contains(github.head_ref, 'xsoar-bot-contrib-ContributionTestPack') == false && contains(github.event.pull_request.labels.*.name, 'Contribution Form Filled') == false
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v2
13+
with:
14+
ref: master
15+
- name: Setup Python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: '3.8'
19+
- name: Install Python Dependencies
20+
run: |
21+
python -m pip install --upgrade pip
22+
pip install pipenv
23+
cd Utils/github_workflow_scripts
24+
pipenv install --dev
25+
- name: Check if contribution form needs to be filled
26+
env:
27+
PR_NUMBER: ${{ github.event.pull_request.number }}
28+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29+
run: |
30+
echo "Checking if contribution form needs to be filled for PR: $PR_NUMBER"
31+
cd Utils/github_workflow_scripts
32+
pipenv run ./check_if_needs_to_fill_contribution_form.py --pr_number $PR_NUMBER --github_token $GITHUB_TOKEN
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import sys
6+
import re
7+
import json
8+
import base64
9+
import urllib3
10+
from github import Github
11+
from github.PullRequest import PullRequest
12+
from github.Repository import Repository
13+
from github.ContentFile import ContentFile
14+
from github.Branch import Branch
15+
16+
from utils import load_json, CONTENT_ROOT_PATH, timestamped_print
17+
18+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
19+
print = timestamped_print
20+
21+
METADATA = 'pack_metadata.json'
22+
PACKS = 'Packs'
23+
SUPPORT = 'support'
24+
XSOAR_SUPPORT = 'xsoar'
25+
PACK_NAME_REGEX = re.compile(r'Packs/([A-Za-z0-9-_.]+)/')
26+
27+
28+
def get_metadata_filename_from_pr(pr_files, pack_name) -> str:
29+
""" Iterates over all pr files and return the pr metadata.json filename if exists, else None
30+
31+
Args:
32+
pr_files (PaginatedList[File]): The list of pr files
33+
pack_name (str): The pack name
34+
35+
Returns:
36+
The pr metadata.json filename if exists, else None
37+
38+
"""
39+
pack_metadata_path = os.path.join(PACKS, pack_name, METADATA)
40+
print(f'Searching for a {pack_metadata_path} file in the PR.')
41+
42+
for file in pr_files:
43+
if pack_metadata_path in file.filename:
44+
print(f'Found {METADATA} file in PR for pack {pack_name}: {file.filename}.')
45+
return file.filename
46+
47+
print(f'Did not find a {pack_metadata_path} file in the PR.')
48+
return ''
49+
50+
51+
def get_pack_support_type_from_pr_metadata_file(pr_metadata_filename: str, pr: PullRequest):
52+
""" Retrieves the support type from the pr metadata.json file
53+
54+
Args:
55+
pr_metadata_filename: The pr metadata.json filename
56+
pr: The pr
57+
58+
Returns:
59+
The support type
60+
61+
"""
62+
print(f'Getting support type from {pr_metadata_filename}.')
63+
_, branch_name = pr.head.label.split(':')
64+
print(f'Branch name is: {branch_name}')
65+
contributor_repo: Repository = pr.head.repo
66+
branch: Branch = contributor_repo.get_branch(branch=branch_name)
67+
metadata_file: ContentFile = contributor_repo.get_contents(path=pr_metadata_filename, ref=branch.commit.sha)
68+
metadata_file_content: dict = json.loads(base64.b64decode(metadata_file.content))
69+
return metadata_file_content.get(SUPPORT)
70+
71+
72+
def get_pack_names_from_pr(pr_files) -> set:
73+
""" Extracts the pack names from the pr files
74+
75+
Args:
76+
pr_files (PaginatedList[File]): The list of pr files
77+
78+
Returns:
79+
The set of pack names
80+
81+
"""
82+
pack_names = set()
83+
84+
for file in pr_files:
85+
if PACKS in file.filename:
86+
pack_names.add(re.findall(PACK_NAME_REGEX, file.filename)[0])
87+
88+
if not pack_names:
89+
raise Exception('PR does not contains files prefixed with "Packs".')
90+
91+
return pack_names
92+
93+
94+
def get_pack_support_type_from_repo_metadata_file(pack_name):
95+
""" Retrieves the support type from the repo metadata.json file
96+
97+
Args:
98+
pack_name (str): The pack name
99+
100+
Returns:
101+
The support type
102+
103+
"""
104+
print('Getting support type from the repo.')
105+
repo_pack_metadata_path: str = os.path.join(CONTENT_ROOT_PATH, PACKS, pack_name, METADATA)
106+
print(f'{pack_name} pack {METADATA} file is at path: {repo_pack_metadata_path}')
107+
repo_pack_metadata: dict = load_json(repo_pack_metadata_path)
108+
return repo_pack_metadata.get(SUPPORT)
109+
110+
111+
def arguments_handler():
112+
""" Validates and parses script arguments.
113+
114+
Returns:
115+
Namespace: Parsed arguments object.
116+
117+
"""
118+
parser = argparse.ArgumentParser(description='Check if the contribution form needs to be filled.')
119+
parser.add_argument('-p', '--pr_number', help='The PR number to check if the contribution form needs to be filled.')
120+
parser.add_argument('-g', '--github_token', help='The GitHub token to authenticate the GitHub client.')
121+
return parser.parse_args()
122+
123+
124+
def main():
125+
options = arguments_handler()
126+
pr_number = options.pr_number
127+
github_token = options.github_token
128+
129+
org_name: str = 'demisto'
130+
repo_name: str = 'content'
131+
exit_status = 0
132+
packs_without_metadata_or_support = set()
133+
not_filled_packs = set()
134+
135+
github_client: Github = Github(github_token, verify=False)
136+
content_repo: Repository = github_client.get_repo(f'{org_name}/{repo_name}')
137+
pr: PullRequest = content_repo.get_pull(int(pr_number))
138+
pr_files = pr.get_files()
139+
140+
for pack_name in get_pack_names_from_pr(pr_files):
141+
if pr_metadata_filename := get_metadata_filename_from_pr(pr_files, pack_name):
142+
support_type = get_pack_support_type_from_pr_metadata_file(pr_metadata_filename, pr)
143+
else:
144+
support_type = get_pack_support_type_from_repo_metadata_file(pack_name)
145+
146+
if not support_type:
147+
packs_without_metadata_or_support.add(pack_name)
148+
exit_status = 1
149+
elif support_type == XSOAR_SUPPORT:
150+
print(f'\n{pack_name} pack is XSOAR supported. Contribution form should not be filled for XSOAR supported '
151+
f'contributions.')
152+
else:
153+
not_filled_packs.add(pack_name)
154+
print(f'{pack_name} pack is {support_type} supported.')
155+
exit_status = 1
156+
157+
if packs_without_metadata_or_support:
158+
print(f'ERROR: {METADATA} file / pack support is missing for the following packs: '
159+
f'{packs_without_metadata_or_support}')
160+
161+
if not_filled_packs:
162+
print(f'\nERROR: Contribution form was not filled for PR: {pr_number}.\nMake sure to register your contribution'
163+
f' by filling the contribution registration form in - https://forms.gle/XDfxU4E61ZwEESSMA')
164+
165+
sys.exit(exit_status)
166+
167+
168+
if __name__ == "__main__":
169+
main()

Utils/github_workflow_scripts/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
11
#!/usr/bin/env python3
22

33
import os
4+
import json
45
from datetime import datetime
56
from typing import Optional
67

8+
CONTENT_ROOT_PATH = os.path.abspath(os.path.join(__file__, '../../..')) # full path to content root repo
9+
710
# override print so we have a timestamp with each print
811
org_print = print
912

1013

14+
def load_json(file_path: str) -> dict:
15+
""" Reads and loads json file.
16+
17+
Args:
18+
file_path (str): full path to json file.
19+
20+
Returns:
21+
dict: loaded json file.
22+
23+
"""
24+
try:
25+
if file_path and os.path.exists(file_path):
26+
with open(file_path, 'r') as json_file:
27+
result = json.load(json_file)
28+
else:
29+
result = {}
30+
return result
31+
except json.decoder.JSONDecodeError:
32+
return {}
33+
34+
1135
def timestamped_print(*args, **kwargs):
1236
org_print(datetime.now().strftime('%H:%M:%S.%f'), *args, **kwargs)
1337

0 commit comments

Comments
 (0)