From 669cce7c9df93a0a540c0cde749fa32bd8608245 Mon Sep 17 00:00:00 2001 From: Sebastien Gioria Date: Fri, 5 Dec 2025 16:37:15 +0100 Subject: [PATCH] chore: update GitHub Actions to use latest actions and add repository listing script --- .github/workflows/python-app.yml | 9 +- githubTools/devsecops/listAllgithubRepos | 133 +++++++++++++++++++++++ githubTools/devsecops/requirements.txt | 1 + 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 githubTools/devsecops/listAllgithubRepos create mode 100644 githubTools/devsecops/requirements.txt diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index e559e1c..e13d861 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -15,9 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: 3.9 - name: Install dependencies @@ -34,3 +34,8 @@ jobs: - name: Test with pytest run: | pytest + - name: Run repository listing script + run: | + python githubTools/devsecops/listAllgithubRepos.py https://github.com/SPoint42 --format https + env: + GITHUB_TOKEN: ${{ secrets.GH_SEC_TOOLS_PAT }} diff --git a/githubTools/devsecops/listAllgithubRepos b/githubTools/devsecops/listAllgithubRepos new file mode 100644 index 0000000..a87ec44 --- /dev/null +++ b/githubTools/devsecops/listAllgithubRepos @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +import argparse +import os +import requests +from urllib.parse import urlparse + +# You can generate a personal access token at: +# https://github.com/settings/tokens +# This is optional for public repos but increases rate limits +# and is required for private repos. +GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") + +def get_entity_type(name, headers): + """ + Determines if a GitHub entity is a User or an Organization. + """ + api_url = f"https://api.github.com/users/{name}" + response = requests.get(api_url, headers=headers) + response.raise_for_status() # Raises an exception for bad status codes + return response.json().get("type") + +def get_all_repos(repos_url, headers): + """ + Retrieves all repositories from a paginated GitHub API endpoint. + """ + repos = [] + page = 1 + while True: + paginated_url = f"{repos_url}?page={page}&per_page=100" + response = requests.get(paginated_url, headers=headers) + response.raise_for_status() + + data = response.json() + if not data: + # No more repositories on this page, we are done. + break + + repos.extend(data) + page += 1 + + # Check Link header for next page to be more robust, though incrementing page works well. + if 'next' not in response.links: + break + + return repos + +def list_github_repos(github_url, token=None): + """ + Lists all repositories for a given GitHub user or organization URL. + + Args: + github_url (str): The root URL of the GitHub user or organization + (e.g., https://github.com/google). + token (str, optional): A GitHub Personal Access Token for authentication. + + Returns: + list: A list of dictionaries, where each dictionary contains + details about a repository. + """ + parsed_url = urlparse(github_url) + path_parts = parsed_url.path.strip('/').split('/') + + if not path_parts or not path_parts[0]: + raise ValueError("Invalid GitHub URL. Could not extract user/org name.") + + name = path_parts[0] + print(f"[*] Fetching repositories for '{name}'...") + + headers = {"Accept": "application/vnd.github.v3+json"} + if token: + headers["Authorization"] = f"token {token}" + + try: + entity_type = get_entity_type(name, headers) + print(f"[*] '{name}' is an {entity_type}.") + + if entity_type == "User": + repos_url = f"https://api.github.com/users/{name}/repos" + elif entity_type == "Organization": + repos_url = f"https://api.github.com/orgs/{name}/repos" + else: + print(f"[!] Unknown entity type: {entity_type}") + return [] + + all_repos = get_all_repos(repos_url, headers) + return all_repos + + except requests.exceptions.HTTPError as e: + print(f"[!] Error fetching data from GitHub API: {e}") + if e.response.status_code == 404: + print(f"[!] The user or organization '{name}' could not be found.") + elif e.response.status_code == 401: + print("[!] Authentication error. Please check your GITHUB_TOKEN.") + return [] + except requests.exceptions.RequestException as e: + print(f"[!] A network error occurred: {e}") + return [] + +def main(): + """Main function to parse arguments and list repositories.""" + parser = argparse.ArgumentParser( + description="List all repositories for a GitHub user or organization.", + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "url", + help="The GitHub URL of the user or organization (e.g., https://github.com/torvalds)" + ) + parser.add_argument( + "--format", + choices=['name', 'ssh', 'https'], + default='name', + help="Output format for the repositories:\n" + " name: Just the repository name (default)\n" + " ssh: The SSH clone URL\n" + " https: The HTTPS clone URL" + ) + args = parser.parse_args() + + repositories = list_github_repos(args.url, GITHUB_TOKEN) + + if repositories: + print(f"\n[+] Found {len(repositories)} repositories:") + for repo in repositories: + if args.format == 'ssh': + print(repo['ssh_url']) + elif args.format == 'https': + print(repo['clone_url']) + else: # 'name' + print(repo['name']) + +if __name__ == "__main__": + main() diff --git a/githubTools/devsecops/requirements.txt b/githubTools/devsecops/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/githubTools/devsecops/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file