Skip to content

Commit

Permalink
Initial implementation of ET comments
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaarreell committed Jan 3, 2025
1 parent dbfffe1 commit 7988505
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 11 deletions.
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Below is an example of such a file.
```
[erratatool]
url = https://..
enable_comments = 1
[jira]
url = https://...
token = *JIRATOKEN*
Expand All @@ -45,6 +46,7 @@ recheck_delay = 120
This settings can be overriden by environment variables that take precedence.
```
NEWA_ET_URL
NEWA_ET_ENABLE_COMMENTS
NEWA_JIRA_URL
NEWA_JIRA_TOKEN
NEWA_JIRA_PROJECT
Expand Down Expand Up @@ -88,12 +90,25 @@ defaults:
issues:
- summary: "Errata Workflow Checklist {% if ERRATUM.respin_count > 0 %}(respin {{ ERRATUM.respin_count }}){% endif %}"
description: "Task tracking particular respin of errata."
- summary: "ER#{{ ERRATUM.id }} - {{ ERRATUM.summary }} (testing)"
description: "{{ ERRATUM.url }}\n{{ ERRATUM.components|join(' ') }}"
type: epic
id: errata_epic
on_respin: keep
erratum_comment_triggers:
- jira
- summary: "ER#{{ ERRATUM.id }} - Sanity and regression testing {{ ERRATUM.builds|join(' ') }}"
description: "Run all automated tests"
type: task
id: errata_task
id: task_regression
parent_id: errata_epic
on_respin: close
auto_transition: True
job_recipe: https://path/to/my/NEWA/recipe/errata.yaml
erratum_comment_triggers:
- execute
- report
```

Individual settings are described below.
Expand Down Expand Up @@ -166,6 +181,10 @@ The following options are available:
- `parent_id`: refers to item `id` which should become a parent Jira issue of this issue.
- `on_respin`: Defines action when the issue is obsoleted by a newer version (due to erratum respin). Possible values are `close` (i.e. create a new issue) and `keep` (i.e. reuse existing issue).
- `auto_transition`: Defines if automatic issue state transitions are enabled (`True`) or not (`False`, a default value).
- `erratum_comment_triggers` - For specified triggers, provides an update in an erratum through a comment. This functionality needs to be enabled also in the `newa.conf` file through `enable_comments = 1`. The following triggers are currently supported:
- `jira` - Adds a comment when a Jira issue is initially 'adopted' by NEWA (either created or taken over due to `jira --map-issue` parameter).
- `execute` - Adds a comment when automated tests are initiated by NEWA.
- `report` - Adds a comment when automated tests results are reported by NEWA.
- `when`: A condition that restricts when an item should be used. See "In-config tests" section for examples.

### Recipe config file
Expand Down
106 changes: 99 additions & 7 deletions newa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import urllib3.response
from requests_kerberos import HTTPKerberosAuth

HTTP_STATUS_CODES_OK = [200, 201]

if TYPE_CHECKING:
from typing import ClassVar

Expand Down Expand Up @@ -76,6 +78,7 @@ def _represent_enum(

yaml.representer.add_representer(EventType, _represent_enum)
yaml.representer.add_representer(ErratumContentType, _represent_enum)
yaml.representer.add_representer(ErratumCommentTrigger, _represent_enum)
yaml.representer.add_representer(Arch, _represent_enum)

return yaml
Expand Down Expand Up @@ -137,6 +140,7 @@ class Settings:
""" Class storing newa settings """

et_url: str = ''
et_enable_comments: bool = False
rp_url: str = ''
rp_token: str = ''
rp_project: str = ''
Expand Down Expand Up @@ -166,11 +170,19 @@ def _get(
# then attempt to use the value from config file, use fallback value otherwise
return env if env else cp.get(section, key, fallback=str(default))

def _str_to_bool(value: str) -> bool:
return value.strip().lower() in ['1', 'true']

return Settings(
et_url=_get(
cp,
'erratatool/url',
'NEWA_ET_URL'),
et_enable_comments=_str_to_bool(
_get(
cp,
'erratatool/enable_comments',
'NEWA_ET_ENABLE_COMMENTS')),
rp_url=_get(
cp,
'reportportal/url',
Expand Down Expand Up @@ -275,7 +287,7 @@ def get_request(
url,
auth=HTTPKerberosAuth(delegate=True),
) if krb else requests.get(url)
if r.status_code == 200:
if r.status_code in HTTP_STATUS_CODES_OK:
response = getattr(r, response_content.value)
if callable(response):
return response()
Expand All @@ -289,6 +301,59 @@ def get_request(
raise Exception(f"GET request to {url} failed")


@overload
def post_request(
*,
url: str,
json: JSON,
krb: bool = False,
attempts: int = 5,
delay: int = 5,
response_content: Literal[ResponseContentType.RAW]) -> urllib3.response.HTTPResponse:
pass


@overload
def post_request(
*,
url: str,
json: JSON,
krb: bool = False,
attempts: int = 5,
delay: int = 5,
response_content: Literal[ResponseContentType.JSON]) -> JSON:
pass


def post_request(
url: str,
json: JSON,
krb: bool = False,
attempts: int = 5,
delay: int = 5,
response_content: ResponseContentType = ResponseContentType.TEXT) -> Any:
""" Generic POST request, optionally using Kerberos authentication """
while attempts:
try:
r = requests.post(
url,
json=json,
auth=HTTPKerberosAuth(delegate=True),
) if krb else requests.post(url, json=json)
if r.status_code in HTTP_STATUS_CODES_OK:
response = getattr(r, response_content.value)
if callable(response):
return response()
return response
except requests.exceptions.RequestException:
# will give it another try
pass
time.sleep(delay)
attempts -= 1

raise Exception(f"POST request to {url} failed")


def eval_test(
test: str,
environment: Optional[jinja2.Environment] = None,
Expand Down Expand Up @@ -439,6 +504,16 @@ class ErrataTool:

url: str = field(validator=validators.matches_re("^https?://.+$"))

def add_comment(self, erratum_id: str, comment: str) -> JSON:
query_data: JSON = {"comment": comment}
return post_request(
url=urllib.parse.urljoin(
self.url,
f"/api/v1/erratum/{erratum_id}/add_comment"),
json=query_data,
krb=True,
response_content=ResponseContentType.JSON)

def fetch_info(self, erratum_id: str) -> JSON:
return get_request(
url=urllib.parse.urljoin(
Expand Down Expand Up @@ -631,10 +706,13 @@ class Erratum(Cloneable, Serializable): # type: ignore[no-untyped-def]


@define
class Issue(Cloneable, Serializable):
class Issue(Cloneable, Serializable): # type: ignore[no-untyped-def]
""" Issue - a key in Jira (eg. NEWA-123) """

id: str = field()
erratum_comment_triggers: list[ErratumCommentTrigger] = field( # type: ignore[var-annotated]
factory=list, converter=lambda triggers: [
ErratumCommentTrigger(trigger) for trigger in triggers])
# this is used to store comment visibility restriction
# usually JiraHandler.group takes priority but this value
# will be used when JiraHandler is not available
Expand Down Expand Up @@ -1131,6 +1209,12 @@ class OnRespinAction(Enum):
CLOSE = 'close'


class ErratumCommentTrigger(Enum):
JIRA = 'jira'
EXECUTE = 'execute'
REPORT = 'report'


def _default_action_id_generator() -> Generator[str, int, None]:
n = 1
while True:
Expand All @@ -1146,6 +1230,9 @@ class IssueAction(Serializable): # type: ignore[no-untyped-def]
type: IssueType = field(converter=IssueType, default=IssueType.TASK)
on_respin: OnRespinAction = field( # type: ignore[var-annotated]
converter=lambda value: OnRespinAction(value), default=OnRespinAction.CLOSE)
erratum_comment_triggers: list[ErratumCommentTrigger] = field( # type: ignore[var-annotated]
factory=list, converter=lambda triggers: [
ErratumCommentTrigger(trigger) for trigger in triggers])
auto_transition: Optional[bool] = False
summary: Optional[str] = None
description: Optional[str] = None
Expand Down Expand Up @@ -1532,21 +1619,25 @@ def create_issue(self,
except jira.JIRAError as e:
raise Exception("Unable to create issue!") from e

def refresh_issue(self, action: IssueAction, issue: Issue) -> None:
""" Update NEWA identifier of issue """
def refresh_issue(self, action: IssueAction, issue: Issue) -> bool:
""" Update NEWA identifier of issue.
Returns True when the issue had been 'adopted' by NEWA."""

issue_details = self.get_details(issue)
description = issue_details.fields.description
labels = issue_details.fields.labels
new_description = ""
return_value = False

# add NEWA label if missing
if self.newa_label not in labels:
issue_details.add_field_value('labels', self.newa_label)
return_value = True

# Issue does not have any NEWA ID yet
if isinstance(description, str) and self.newa_id() not in description:
new_description = f"{self.newa_id(action)}\n{description}"
return_value = True

# Issue has NEWA ID but not the current respin - update it.
elif isinstance(description, str) and self.newa_id(action) not in description:
Expand All @@ -1560,6 +1651,7 @@ def refresh_issue(self, action: IssueAction, issue: Issue) -> None:
issue, "NEWA refreshed issue ID.")
except jira.JIRAError as e:
raise Exception(f"Unable to modify issue {issue}!") from e
return return_value

def comment_issue(self, issue: Issue, comment: str) -> None:
""" Add comment to issue """
Expand Down Expand Up @@ -1690,7 +1782,7 @@ def get_request(self,
url = f'{url}?{urllib.parse.urlencode(params)}'
headers = {"Authorization": f"bearer {self.token}", "Content-Type": "application/json"}
req = requests.get(url, headers=headers)
if req.status_code == 200:
if req.status_code in HTTP_STATUS_CODES_OK:
return req.json()
return None

Expand All @@ -1702,7 +1794,7 @@ def put_request(self,
self.url, f'/api/v{version}/{Q(self.project)}/{Q(path.lstrip("/"))}')
headers = {"Authorization": f"bearer {self.token}", "Content-Type": "application/json"}
req = requests.put(url, headers=headers, json=json)
if req.status_code == 200:
if req.status_code in HTTP_STATUS_CODES_OK:
return req.json()
return None

Expand All @@ -1715,7 +1807,7 @@ def post_request(self,
f'/api/v{version}/{Q(self.project)}/{Q(path.lstrip("/"))}')
headers = {"Authorization": f"bearer {self.token}", "Content-Type": "application/json"}
req = requests.post(url, headers=headers, json=json)
if req.status_code in [200, 201]:
if req.status_code in HTTP_STATUS_CODES_OK:
return req.json()
return None

Expand Down
Loading

0 comments on commit 7988505

Please sign in to comment.