Skip to content

Commit

Permalink
Support get a product's release' all tasks from Product Pages
Browse files Browse the repository at this point in the history
Created an client to connect to PP for release api
Updated README on some command and links
Updated Docker and CI files with krb5 install to avoid ci failure
Rename customize logging class, otherwise it confused with internal logging class
Adding Tests to test coverage 100%

JIRA: RHELWF-10975
  • Loading branch information
dependabot[bot] authored and Lu Zhang committed Jun 21, 2024
1 parent 21303e6 commit 9f402d5
Show file tree
Hide file tree
Showing 14 changed files with 742 additions and 46 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/gating.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ jobs:
with:
python-version: "3.12"

- name: Install system dependencies
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
retry_wait_seconds: 30
max_attempts: 3
command: >-
sudo apt-get update
&& sudo apt-get install
libkrb5-dev
- name: Install dependencies
run: python -m pip install tox

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/.mypy_cache
/.ruff_cache
/.tox
__pycache__/
2 changes: 2 additions & 0 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUN set -exo pipefail \
--disablerepo=* \
--enablerepo=fedora,updates \
gcc \
krb5-devel \
python3 \
python3-devel \
# install runtime dependencies
Expand All @@ -20,6 +21,7 @@ RUN set -exo pipefail \
--nodocs \
--disablerepo=* \
--enablerepo=fedora,updates \
krb5-libs \
python3 \
&& yum --installroot=/mnt/rootfs clean all \
&& rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* \
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ Inspect specific test failure:
tox -e py3 -- --no-cov -lvvvvsxk test_init_tracing_with_valid_config
```

Install poetry with [pipx]:

```
# On macOS
brew install pipx
pipx ensurepath
# On Fedora
sudo dnf install pipx
pipx ensurepath
# Install poetry
pipx install poetry
```

Install and run the app to virtualenv with [Poetry]:

```
Expand All @@ -100,4 +115,9 @@ Clean up the virtualenv (can be useful after larger host system updates):
poetry env remove --all
```

If you are not familiar with those please watch this [Tutorial] to have some ideas on what are
poetry, pre-commit, flake, tox, etc...

[pipx]: https://pipx.pypa.io/stable/
[Poetry]: https://python-poetry.org/docs/
[Tutorial]: https://www.linkedin.com/learning/create-an-open-source-project-in-python
372 changes: 339 additions & 33 deletions poetry.lock

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ opentelemetry-instrumentation-flask = "^0.45b0"
opentelemetry-instrumentation-requests = "^0.45b0"
opentelemetry-exporter-otlp-proto-http = "^1.24.0"

# for tests
pytest = {version = "^8.2.0", optional = true}
pytest-cov = {version = "^5.0.0", optional = true}
requests = "^2.32.0"
requests-kerberos = "^0.14.0"

[tool.poetry.extras]
test = [
"pytest",
"pytest-cov",
]
urllib3 = "^2.2.1"
attrs = "^23.2.0"

[tool.poetry.group.dev.dependencies]
requests-mock = "^1.12.1"
pytest = "^8.2.2"
pytest-cov = "^5.0.0"

[tool.poetry.scripts]
retasc = "retasc.__main__:main"
Expand Down
2 changes: 1 addition & 1 deletion src/retasc/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from retasc import __doc__ as doc
from retasc import __version__
from retasc.logging import init_logging
from retasc.retasc_logging import init_logging
from retasc.tracing import init_tracing


Expand Down
129 changes: 129 additions & 0 deletions src/retasc/product_pages_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@

# SPDX-License-Identifier: GPL-3.0-or-later
"""
Production Pages API
"""

from functools import cache
from retasc import requests_session

class ProductPagesApi:
"""
Product Pages API Client
"""
def __init__(self, api_url: str):
self.api_url = api_url
self.session = requests_session.requests_session()

def api_url_releases(self, id: int | None = None) -> str:
return f"{self.api_url.rstrip('/')}/releases/{id or ''}"

@cache
def releases_by_product_phase(
self,
product_shortname: str,
active_only: bool = True,
fields: str = "shortname,product_shortname,ga_date,phase_display",
) -> list[dict]:
"""
https://{pp_release_url}?product__shortname={product_shortname}&phase__lt=Unsupported&fields=a,b,c
The response have the following fields:shortname,product_shortname,ga_date,phase_display
phase__lt=Unsupported means "any supported phase" (these are less than Unsupported).
"""
Unsupported_phase_id = 1000

opt = {
"product__shortname": product_shortname,
"fields": fields,
}
if active_only is True:
opt.update({"phase__lt": Unsupported_phase_id})
res = self.session.get(self.api_url_releases(), params=opt)
res.raise_for_status()
return res.json()

@cache
def release_schedules(
self,
release_short_name: str,
fields: str = "name,date_start,date_finish,flags",
) -> list[dict]:
"""
https://{pp_release_url}{release_short_name}/schedule-tasks?fields=a,b,c
The response have the following fields:name,,date_start,date_finish,flags
"""

res = self.session.get(
f"{self.api_url_releases()}{release_short_name}/schedule-tasks",
params={
"fields": fields,
},
)
res.raise_for_status()
return res.json()

@cache
def release_schedules_by_flags(
self,
release_short_name: str,
flag_cond: str = "flags_or__in",
flags: str = "rcm,releng,exd,sp",
fields: str = "release_shortname,name,date_start,date_finish",
) -> list[dict]:
"""
https://{pp_release_url}{release_short_name}/schedule-tasks/?{flag_cond}={flags}&fields=a,b,c
Only one flag in condition use `flags_in=sp`
Multiple flags in OR condition use `flags_or__in=sp,rcm,releng`
Multiple flags in AND condition use `flags_and__in=sp,ga`
The response have the following fields:release_shortname,name,slug,date_start,date_finish
"""

res = self.session.get(
f"{self.api_url_releases()}{release_short_name}/schedule-tasks",
params={
flag_cond: flags,
"fields": fields,
},
)
res.raise_for_status()
return res.json()

@cache
def release_schedules_by_name_search(
self,
release_short_name: str,
condition: str,
fields: str = "release_shortname,name,slug,date_start,date_finish",
) -> list[dict]:
"""
https://{pp_release_url}{release_short_name}/schedule-tasks?name__regex={condition}&fields=a,b,c
The response have the following fields:release_shortname,name,slug,date_start,date_finish
"""

res = self.session.get(
f"{self.api_url_releases()}{release_short_name}/schedule-tasks",
params={
"name__regex": condition,
"fields": fields,
},
)
res.raise_for_status()
return res.json()

@cache
def get_all_active_releases_tasks_for_product(self, product_shortname: str) -> dict:
"""
Get all release names that are still supported by rest api:
https://{pp_release_url}?product__shortname={product_shortname}&phase__lt=Unsupported&fields=shortname
Loop through the releases and get the tasks of it by rest api
https://{pp_release_url}{release_short_name}/schedule-tasks/fields=name,slug,date_start,date_finish
"""

releases_list = self.releases_by_product_phase(product_shortname)
all_tasks: dict = {}
all_tasks[product_shortname] = {}
for r in releases_list:
r_name = r["shortname"]
r_tasks = self.release_schedules(r_name)
all_tasks[product_shortname][r_name] = r_tasks
return all_tasks
27 changes: 27 additions & 0 deletions src/retasc/requests_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Http Request client with retry and kerberos capabilities
"""

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry


def requests_session():
"""Returns https session for request processing."""

session = requests.Session()
retry = Retry(
total=3,
read=3,
connect=3,
backoff_factor=1,
status_forcelist=(500, 502, 503, 504),
allowed_methods=Retry.DEFAULT_ALLOWED_METHODS.union(("POST",)),
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("https://", adapter)
session.headers["User-Agent"] = "sp-retasc-agent"
session.headers["Content-type"] = "application/json"
return session
File renamed without changes.
6 changes: 3 additions & 3 deletions tests/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import json
from unittest.mock import patch

from retasc.logging import DEFAULT_LOGGING_CONFIG, init_logging
from retasc.retasc_logging import DEFAULT_LOGGING_CONFIG, init_logging


def test_init_logging_with_default_config(monkeypatch):
monkeypatch.setenv("RETASC_LOGGING_CONFIG", "")
with patch("retasc.logging.logging.config") as mock:
with patch("retasc.retasc_logging.logging.config") as mock:
init_logging()
mock.dictConfig.assert_called_once_with(DEFAULT_LOGGING_CONFIG)

Expand All @@ -18,6 +18,6 @@ def test_init_logging_with_custom_config(tmp_path, monkeypatch):
config_file.write_text(json.dumps(config))
monkeypatch.setenv("RETASC_LOGGING_CONFIG", str(config_file))
init_logging()
with patch("retasc.logging.logging.config") as mock:
with patch("retasc.retasc_logging.logging.config") as mock:
init_logging()
mock.dictConfig.assert_called_once_with(config)
Loading

0 comments on commit 9f402d5

Please sign in to comment.