Skip to content

Commit

Permalink
feat: generating reports automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
kiraum committed May 17, 2024
2 parents 52cc7fd + f063faa commit 66fc542
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 23 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/generate_reports.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ jobs:
report_ixbr:
runs-on: ubuntu-latest
timeout-minutes: 360
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install --no-cache-dir -U pip uv
uv pip install --system --break-system-packages -r requirements.txt
- uses: actions/checkout@v3
- name: Generate reports
run: python3 peering_gossip.py -lg https://lg.ix.br
- name: Commit report
Expand All @@ -28,8 +40,20 @@ jobs:
report_amsix:
runs-on: ubuntu-latest
timeout-minutes: 360
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install --no-cache-dir -U pip uv
uv pip install --system --break-system-packages -r requirements.txt
- uses: actions/checkout@v3
- name: Generate reports
run: python3 peering_gossip.py -lg https://lg.ams-ix.net
- name: Commit report
Expand All @@ -46,8 +70,20 @@ jobs:
report_decix:
runs-on: ubuntu-latest
timeout-minutes: 360
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install --no-cache-dir -U pip uv
uv pip install --system --break-system-packages -r requirements.txt
- uses: actions/checkout@v3
- name: Generate reports
run: python3 peering_gossip.py -lg https://lg.de-cix.net
- name: Commit report
Expand All @@ -64,8 +100,20 @@ jobs:
report_linx:
runs-on: ubuntu-latest
timeout-minutes: 360
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install --no-cache-dir -U pip uv
uv pip install --system --break-system-packages -r requirements.txt
- uses: actions/checkout@v3
- name: Generate reports
run: python3 peering_gossip.py -lg https://alice-rs.linx.net
- name: Commit report
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint requests pyaml
pip install --no-cache-dir -U pip uv
uv pip install --system --break-system-packages -r requirements.txt
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
4 changes: 2 additions & 2 deletions peering_gossip.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ def main():
pgossip = PGossip()

if args.lg is not None:
pgossip.alice_hos(args.lg)
pgossip.alice_host(args.lg)

if args.all is True:
ixps = pgossip.load_yaml()
for ixp in ixps["ixps"]:
pgossip.alice_hos(ixp)
pgossip.alice_host(ixp)

if options is False:
if len(sys.argv) == 1:
Expand Down
108 changes: 94 additions & 14 deletions pgossip/pgossip.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,64 @@
import sys
import time

import backoff
import requests
import yaml


class PGossip:
class RetryMeta(type):
"""
Peering Buddy main functions for analyzing peering information.
A metaclass that applies retry logic to all callable methods of a class using backoff.
Provides methods for generating a hall of shame, fetching information about alive route servers
and their neighbors, retrieving ASN whois information, creating a report, and loading a YAML config file.
This metaclass enhances methods to retry up to 5 times upon `requests.exceptions.RequestException`,
using an exponential backoff strategy. It automatically decorates all methods that are not special
methods (not starting with '__').
Attributes:
None
name (str): The name of the class.
bases (tuple): The base classes of the class.
dct (dict): The dictionary containing the class's attributes.
Returns:
type: The new class with modified methods.
"""

def __new__(mcs, name, bases, dct):
for key, value in dct.items():
if callable(value) and not key.startswith("__"):
dct[key] = backoff.on_exception(
backoff.expo, requests.exceptions.RequestException, max_tries=5
)(value)
return type.__new__(mcs, name, bases, dct)


class PGossip(metaclass=RetryMeta):
"""
A class to handle peering and routing information gathering and reporting.
This class provides methods to fetch and analyze routing data from specified URLs,
generate reports based on the gathered data, and manage interactions with external APIs
for data retrieval and report generation.
Methods:
alice_hos: Generate hall of shame based on provided URL.
alice_rs: Get alive looking glass route servers.
alice_neighbours: Get alive looking glass neighbors for a specific route server.
bv_asn_whois: Return ASN whois information from BGPView API.
create_report: Create a pastebin-like report using glot.io API.
load_yaml: Load a YAML config file.
alice_host(url): Main method to process routing data for a given URL.
parse_text_to_json(data_text): Converts text data into JSON format.
write_report_to_file(fname, data, as_json): Writes data to a file in text or JSON format.
alice_rs(url): Fetches alive route servers from a given URL.
alice_neighbours(url, route_server): Fetches routing neighbors for a specific route server.
bv_asn_whois(asn): Retrieves ASN WHOIS information from the BGPView API.
create_report(data): Creates a pastebin-like report.
load_yaml(): Loads a YAML configuration file.
Attributes:
None explicitly defined; configuration and state are managed internally within methods.
"""

def __init__(self):
pass

# pylint: disable=too-many-locals
def alice_hos(self, url):
def alice_host(self, url):
"""
Generate hall of shame based on provided URL.
Expand Down Expand Up @@ -87,10 +120,56 @@ def alice_hos(self, url):
report_link = self.create_report("\n".join(map(str, text)))
print("=" * 80)
print(f"We created a sharable report link, enjoy => {report_link}")
fwrite = f"reports/{fname}.txt"
self.write_report_to_file(fname, "\n".join(map(str, text)), as_json=False)
self.write_report_to_file(fname, "\n".join(map(str, text)), as_json=True)

def parse_text_to_json(self, data_text):
"""
Convert a list of delimited text data into a list of dictionaries.
Args:
data_text (str): A string containing multiple lines of data, each line is a delimited record.
Returns:
list: A list of dictionaries with parsed data.
"""
lines = data_text.strip().split("\n")
headers = [header.strip() for header in lines[0].split("|")]
json_data = []

for line in lines[1:]:
values = [value.strip() for value in line.split("|")]
entry = dict(zip(headers, values))
json_data.append(entry)
return json_data

def write_report_to_file(self, fname: str, data: list, as_json: bool = False):
"""
Write data to a file, creating the necessary directories if they do not exist.
The data can be written as plain text or as JSON.
Args:
fname (str): The filename (without extension) where the data will be saved.
data (list): A list of data entries, each entry can be a string or a dictionary.
as_json (bool): If True, writes the data in JSON format. Otherwise, writes as plain text.
Example:
write_report_to_file("2023-01-01_report", data, as_json=True)
"""
# Construct the full file path with directory and filename
extension = "json" if as_json else "txt"
fwrite = f"reports/{fname}.{extension}"

# Ensure the directory exists; if not, create it
os.makedirs(os.path.dirname(fwrite), exist_ok=True)

# Open the file and write the data to it
with open(fwrite, "w", encoding="utf8") as tfile:
tfile.write("\n".join(map(str, text)))
if as_json:
data = self.parse_text_to_json(data)
json.dump(data, tfile, indent=4)
else:
tfile.write(data)

def alice_rs(self, url):
"""
Expand All @@ -110,6 +189,7 @@ def alice_rs(self, url):
data = json.loads(response.text)
for rserver in data["routeservers"]:
rs_list.append(rserver["id"])
rs_list = ["SP-rs2-v4"]
else:
print("ERROR | HTTP status != 200 - alice_rs")
sys.exit(1)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ requires-python = ">=3.12"
keywords = ["peering", "tool", "shame", "hall"]
dependencies = [
"requests",
"backoff",
"black",
"isort",
"pylint",
Expand Down
11 changes: 6 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml
astroid==3.1.0
astroid==3.2.1
# via pylint
backoff==2.2.1
black==24.4.2
certifi==2024.2.2
# via requests
Expand Down Expand Up @@ -29,21 +30,21 @@ pathspec==0.12.1
# via
# black
# yamllint
platformdirs==4.2.1
platformdirs==4.2.2
# via
# black
# pylint
pluggy==1.5.0
# via pytest
pylint==3.1.0
pylint==3.2.0
pytest==8.2.0
pyyaml==6.0.1
# via yamllint
requests==2.31.0
ruff==0.4.3
ruff==0.4.4
tomlkit==0.12.5
# via pylint
urllib3==2.2.1
# via requests
uv==0.1.41
uv==0.1.44
yamllint==1.35.1

0 comments on commit 66fc542

Please sign in to comment.