Skip to content

Commit 20e3d83

Browse files
authored
Merge pull request #2 from SPoint42/devsecops
chore: update GitHub Actions to use latest actions and add repository…
2 parents fb2b1dc + 669cce7 commit 20e3d83

3 files changed

Lines changed: 141 additions & 2 deletions

File tree

.github/workflows/python-app.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ jobs:
1515
runs-on: ubuntu-latest
1616

1717
steps:
18-
- uses: actions/checkout@v2
18+
- uses: actions/checkout@v4
1919
- name: Set up Python 3.9
20-
uses: actions/setup-python@v2
20+
uses: actions/setup-python@v5
2121
with:
2222
python-version: 3.9
2323
- name: Install dependencies
@@ -34,3 +34,8 @@ jobs:
3434
- name: Test with pytest
3535
run: |
3636
pytest
37+
- name: Run repository listing script
38+
run: |
39+
python githubTools/devsecops/listAllgithubRepos.py https://github.com/SPoint42 --format https
40+
env:
41+
GITHUB_TOKEN: ${{ secrets.GH_SEC_TOOLS_PAT }}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import os
4+
import requests
5+
from urllib.parse import urlparse
6+
7+
# You can generate a personal access token at:
8+
# https://github.com/settings/tokens
9+
# This is optional for public repos but increases rate limits
10+
# and is required for private repos.
11+
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
12+
13+
def get_entity_type(name, headers):
14+
"""
15+
Determines if a GitHub entity is a User or an Organization.
16+
"""
17+
api_url = f"https://api.github.com/users/{name}"
18+
response = requests.get(api_url, headers=headers)
19+
response.raise_for_status() # Raises an exception for bad status codes
20+
return response.json().get("type")
21+
22+
def get_all_repos(repos_url, headers):
23+
"""
24+
Retrieves all repositories from a paginated GitHub API endpoint.
25+
"""
26+
repos = []
27+
page = 1
28+
while True:
29+
paginated_url = f"{repos_url}?page={page}&per_page=100"
30+
response = requests.get(paginated_url, headers=headers)
31+
response.raise_for_status()
32+
33+
data = response.json()
34+
if not data:
35+
# No more repositories on this page, we are done.
36+
break
37+
38+
repos.extend(data)
39+
page += 1
40+
41+
# Check Link header for next page to be more robust, though incrementing page works well.
42+
if 'next' not in response.links:
43+
break
44+
45+
return repos
46+
47+
def list_github_repos(github_url, token=None):
48+
"""
49+
Lists all repositories for a given GitHub user or organization URL.
50+
51+
Args:
52+
github_url (str): The root URL of the GitHub user or organization
53+
(e.g., https://github.com/google).
54+
token (str, optional): A GitHub Personal Access Token for authentication.
55+
56+
Returns:
57+
list: A list of dictionaries, where each dictionary contains
58+
details about a repository.
59+
"""
60+
parsed_url = urlparse(github_url)
61+
path_parts = parsed_url.path.strip('/').split('/')
62+
63+
if not path_parts or not path_parts[0]:
64+
raise ValueError("Invalid GitHub URL. Could not extract user/org name.")
65+
66+
name = path_parts[0]
67+
print(f"[*] Fetching repositories for '{name}'...")
68+
69+
headers = {"Accept": "application/vnd.github.v3+json"}
70+
if token:
71+
headers["Authorization"] = f"token {token}"
72+
73+
try:
74+
entity_type = get_entity_type(name, headers)
75+
print(f"[*] '{name}' is an {entity_type}.")
76+
77+
if entity_type == "User":
78+
repos_url = f"https://api.github.com/users/{name}/repos"
79+
elif entity_type == "Organization":
80+
repos_url = f"https://api.github.com/orgs/{name}/repos"
81+
else:
82+
print(f"[!] Unknown entity type: {entity_type}")
83+
return []
84+
85+
all_repos = get_all_repos(repos_url, headers)
86+
return all_repos
87+
88+
except requests.exceptions.HTTPError as e:
89+
print(f"[!] Error fetching data from GitHub API: {e}")
90+
if e.response.status_code == 404:
91+
print(f"[!] The user or organization '{name}' could not be found.")
92+
elif e.response.status_code == 401:
93+
print("[!] Authentication error. Please check your GITHUB_TOKEN.")
94+
return []
95+
except requests.exceptions.RequestException as e:
96+
print(f"[!] A network error occurred: {e}")
97+
return []
98+
99+
def main():
100+
"""Main function to parse arguments and list repositories."""
101+
parser = argparse.ArgumentParser(
102+
description="List all repositories for a GitHub user or organization.",
103+
formatter_class=argparse.RawTextHelpFormatter
104+
)
105+
parser.add_argument(
106+
"url",
107+
help="The GitHub URL of the user or organization (e.g., https://github.com/torvalds)"
108+
)
109+
parser.add_argument(
110+
"--format",
111+
choices=['name', 'ssh', 'https'],
112+
default='name',
113+
help="Output format for the repositories:\n"
114+
" name: Just the repository name (default)\n"
115+
" ssh: The SSH clone URL\n"
116+
" https: The HTTPS clone URL"
117+
)
118+
args = parser.parse_args()
119+
120+
repositories = list_github_repos(args.url, GITHUB_TOKEN)
121+
122+
if repositories:
123+
print(f"\n[+] Found {len(repositories)} repositories:")
124+
for repo in repositories:
125+
if args.format == 'ssh':
126+
print(repo['ssh_url'])
127+
elif args.format == 'https':
128+
print(repo['clone_url'])
129+
else: # 'name'
130+
print(repo['name'])
131+
132+
if __name__ == "__main__":
133+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests

0 commit comments

Comments
 (0)