Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
steps:
- sidekick/tidy_build_publish:
cache_key: r20230619
cache_files: requirements.txt Makefile Makefile.sidekick
cache_files: requirements.in requirements.txt Makefile Makefile.sidekick
publish: << parameters.publish >>
snyk_python: true
snyk_enabled: true
Expand Down
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* jo@devrev.ai nn@devrev.ai
* jo@devrev.ai
9 changes: 4 additions & 5 deletions .github/workflows/global-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out target repository
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Check out workflow tooling repository
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
repository: devrev/jano-gh-workflow
repository: devrev/global-gh-tools
path: .global-checks-tooling
- name: Load python requirements.
run: |
pip install strictyaml
python -m pip install -r .global-checks-tooling/requirements.txt
- name: Check that there is a CODEOWNERS file
run: |
echo "Check whether the repo has a .github/CODEOWNERS file"
Expand All @@ -42,5 +42,4 @@ jobs:
--no-fail \
--no-update \
--json > .global-checks-tooling/trufflehog.json
cat .global-checks-tooling/trufflehog.json
python .global-checks-tooling/checks/check_trufflehog.py .global-checks-tooling/trufflehog.json
8 changes: 4 additions & 4 deletions .github/workflows/patching-sla-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ jobs:
pull-requests: write
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::162856926107:role/gh-action-security-tools
aws-region: us-east-1
- name: Check out target repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Check out workflow tooling repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
repository: devrev/global-gh-tools
ref: main
path: .global-checks-tooling
- name: Load python requirements.
run: |
pip install strictyaml boto3
python -m pip install -r .global-checks-tooling/requirements.txt
- name: Run patching SLA check
id: patch_check
continue-on-error: true
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/validate-description.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
contents: read
steps:
- name: Check out workflow tooling repository
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
repository: devrev/global-gh-tools
path: .global-checks-tooling
Expand All @@ -40,12 +40,12 @@ jobs:
if [ "$pr_body" == "null" ] || [ -z "$pr_body" ]; then
echo "❌ ERROR: Failed to fetch PR description"
echo ""

if [ -n "$api_message" ]; then
echo "API Message: $api_message"
echo ""
fi

echo "API Response:"
echo "$response" | jq '.'
exit 1
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ deps: $(venv)/.deps
.PHONY: deps

test: $(venv)/.deps
$(py) -m unittest
$(py) -m unittest

update-requirements: $(py)
$(py) -m uv pip compile --python-version $(PYTHON_VERSION) --universal \
--upgrade --custom-compile-command "make update-requirements" \
requirements.in --output-file requirements.txt
.PHONY: update-requirements
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# global-gh-tools

Globally required workflow to enforce:

- The use of a CODEOWNERS file.
- The use of a repo.yml file.
- The use of devrev.ai emails in commits.
- No credentials getting committed.

## Python dependencies

Update Python dependencies with `make update-requirements`.
7 changes: 5 additions & 2 deletions checks/check_codeowners.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import sys

CODEOWNERS_FILE = ".github/CODEOWNERS"


def check_codeowners():
"""
Check if the CODEOWNERS file exists and is not empty.
Expand All @@ -17,9 +19,10 @@ def check_codeowners():
print(f"{CODEOWNERS_FILE} file exists and is not empty.")
return True


if __name__ == "__main__":
if not check_codeowners():
print("Please add a CODEOWNERS file to the repository.")
exit(1)
sys.exit(1)
else:
print("CODEOWNERS file check passed.")
print("CODEOWNERS file check passed.")
5 changes: 4 additions & 1 deletion checks/check_commit_emails.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os
import sys

EMAILS_PATH = ".global-checks-tooling/committers.txt"

WHITELIST = [
"actions@github.com",
]


# Check that the emails in the file are from @devrev.ai.
def check_commit_emails():
"""
Expand All @@ -29,7 +31,8 @@ def check_commit_emails():
print(f"{EMAILS_PATH} file exists and contains valid emails.")
return True


if __name__ == "__main__":
if not check_commit_emails():
print("Please ensure all committer emails are from @devrev.ai.")
exit(1)
sys.exit(1)
58 changes: 36 additions & 22 deletions checks/check_patching_sla.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import json
import sys
import os
import fnmatch
import os
import sys

import boto3
from botocore.exceptions import ClientError

# The list of allowed files/patterns in the patch. If any file outside this list is modified, the patch will be blocked.
# The list includes any file that is necessary for dependency management or build configuration.
Expand All @@ -27,9 +26,10 @@
"pnpm-lock.yaml",
"build.gradle",
"pyproject.toml",
".dockerignore"
".dockerignore",
]


def is_file_allowed(file_path, allowed_patterns):
"""
Check if a file basename matches any of the allowed patterns.
Expand All @@ -49,6 +49,7 @@ def is_file_allowed(file_path, allowed_patterns):

return False


def query_dynamodb_vulns(repo_name):
"""
Query DynamoDB for vulnerability information by repo_name.
Expand All @@ -65,18 +66,23 @@ def query_dynamodb_vulns(repo_name):
region_name = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
table_name = os.environ.get("DYNAMODB_TABLE_NAME", "blocked-repos")

dynamodb = boto3.resource('dynamodb', endpoint_url=endpoint_url, region_name=region_name)
dynamodb = boto3.resource(
"dynamodb", endpoint_url=endpoint_url, region_name=region_name
)
table = dynamodb.Table(table_name)

response = table.get_item(Key={'repo_name': repo_name})
response = table.get_item(Key={"repo_name": repo_name})

if 'Item' in response and 'vulns' in response['Item']:
return response['Item']['vulns']
if "Item" in response and "vulns" in response["Item"]:
return response["Item"]["vulns"]
return None


def check_patching_sla():
if len(sys.argv) != 4:
print("Usage: python check_patching_sla.py <changed_files_path> <repo_name> <comment_file>")
print(
"Usage: python check_patching_sla.py <changed_files_path> <repo_name> <comment_file>"
)
sys.exit(1)

changed_files_path = sys.argv[1]
Expand All @@ -90,25 +96,33 @@ def check_patching_sla():
changed_files = f.read().splitlines()
for file in changed_files:
if not is_file_allowed(file, ALLOWED_FILES):
print(f"Repository {repo_name} is in the blocked repos list. Please see PR comment for details.")
with open(comment_file, 'w') as f:
f.write(f"## ⚠️ Heads-up: This repository will be blocked from any work other than patching.\n")
print(
f"Repository {repo_name} is in the blocked repos list. Please see PR comment for details."
)
with open(comment_file, "w") as f:
f.write(
"## ⚠️ Heads-up: This repository will be blocked from any work other than patching.\n"
)
f.write(f"File {file} is not allowed to be modified in this patch.\n")
f.write("The following vulnerability issues are past SLA:\n")
for issue in vulns:
id = issue['id']
overdue_days = issue['overdue_days']
severity = issue['severity']
owner_email = issue['owner_email']
last_checked = issue['timestamp']
id = issue["id"]
overdue_days = issue["overdue_days"]
severity = issue["severity"]
owner_email = issue["owner_email"]
last_checked = issue["timestamp"]
# Extract issue number from id, it is the last part after the last slash.
issue_number = id.split('/')[-1]
f.write(f"- {issue_number}, overdue {overdue_days:.1f}d, sev {severity}, owner: {owner_email}, checked: {last_checked}\n")
f.write("\nNote that there is significant latency in updating this list. Please reach out on #antifragile if you are in a hurry or have an emergency.\n")
issue_number = id.split("/")[-1]
f.write(
f"- {issue_number}, overdue {overdue_days:.1f}d, sev {severity}, owner: {owner_email}, checked: {last_checked}\n"
)
f.write(
"\nNote that there is significant latency in updating this list. Please reach out on #antifragile if you are in a hurry or have an emergency.\n"
)
return False
return True


if __name__ == "__main__":
if not check_patching_sla():
exit(1)
sys.exit(1)
17 changes: 11 additions & 6 deletions checks/check_pr_description.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import sys


def check_description(description):
"""
Checks if the PR description contains a work item link.
Expand All @@ -22,31 +23,35 @@ def check_description(description):
)
return bool(re.search(regex, description, re.IGNORECASE))


def check_description_cli(description):
"""
CLI version that prints messages and exits.
"""
print(f"Checking PR description: {description}")
if not check_description(description):
print("❌ VALIDATION FAILED")
print("")
print()
print("Reason: PR description must include a work item link")
print("")
print()
print(
"Valid formats: ISS-123, TKT-456, TASK-789, ABC-123, "
"https://app.devrev.ai/devrev/works/ISS-123, or "
"https://app.devrev.ai/devrev/issue/DES-123"
)
print("Note: Use 'work-item: ISS-123' (space after colon), not 'work-item:ISS-123'")
print(
"Note: Use 'work-item: ISS-123' (space after colon), not 'work-item:ISS-123'"
)
sys.exit(1)

print("✅ VALIDATION PASSED - PR description contains a valid work item link")
sys.exit(0)


if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python check_pr_description.py \"<description>\"")
print('Usage: python check_pr_description.py "<description>"')
sys.exit(1)

pr_description = sys.argv[1]
check_description_cli(pr_description)
18 changes: 12 additions & 6 deletions checks/check_repo_yml.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import os
import sys

import strictyaml

ANNOTATION_FILE = ".devrev/repo.yml"

schema = strictyaml.Map({
"deployable": strictyaml.Bool(),
})
schema = strictyaml.Map(
{
"deployable": strictyaml.Bool(),
}
)


def check_repo_yml():
if not os.path.exists(ANNOTATION_FILE):
print(f"Annotation file {ANNOTATION_FILE} does not exist.")
return False

with open(ANNOTATION_FILE) as f:
text = f.read()

try:
# Validate the YAML file against the schema
strictyaml.load(text, schema)
Expand All @@ -23,7 +28,8 @@ def check_repo_yml():
return False
return True


if __name__ == "__main__":
if not check_repo_yml():
print("Please ensure the .devrev/repo.yml file exists and is valid.")
exit(1)
sys.exit(1)
Loading
Loading