Skip to content

Commit

Permalink
Implement the core functionality
Browse files Browse the repository at this point in the history
JIRA: RHELWF-10977
  • Loading branch information
hluk committed Nov 12, 2024
1 parent e7a3d29 commit 66e1148
Show file tree
Hide file tree
Showing 37 changed files with 1,087 additions and 152 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Product Pages (PP).
ReTaSC is meant to run as a batch job regularly to monitor schedules in PP and
create or update Jira issues according to custom rules and Jira templates.

ReTaSC manages the Jira issues it creates until resolved. This means that if a
PP schedule or a rule changes, the related unresolved Jira issues are also
updated or even closed.
ReTaSC creates and manages Jira issues until resolved. This means that if a PP
schedule or a rule changes, the related unresolved Jira issues are also updated
or even closed.

## Tasks and Rules

Expand All @@ -21,15 +21,17 @@ and a Product Release (an identifier in PP).

Rules describe how to manage related tasks using:

- Prerequisites - PP schedule item name with number of days before/after the
date, and list of other dependent Rules
- Prerequisites - list of:
- PP schedule item name, for example:
`schedule_task: "GA for rhel {{ major }}.{{ minor }}"`
- a condition, for example: `condition: "today >= start_date - 3|weeks"`
- reference to other Rule that must be completed
- Jira issue templates
- Definition of Done (DoD) - currently, the only supported DoD is: "related
Jira issues have been resolved"

Task state can be one of:

- Missing (PP schedule is not defined yet)
- Pending (some prerequisites and not satisfied)
- In-progress (prerequisites are satisfied and DoD is not)
- Completed (prerequisites and DoD is satisfied)
Expand All @@ -38,6 +40,10 @@ Task state can be one of:

Below is list of environment variables supported in the container image:

- `RETASC_JIRA_URL` - Jira URL
- `RETASC_JIRA_TOKEN` - Jira access token
- `RETASC_RULES_PATH` - Path to rules
- `RETASC_PP_URL` - Product Pages URL
- `RETASC_LOGGING_CONFIG` - Path to JSON file with the logging configuration;
see details in [Configuration dictionary
schema](https://docs.python.org/3/library/logging.config.html#logging-config-dictschema)
Expand Down
30 changes: 17 additions & 13 deletions examples/rules/rules.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
- version: 1
name: "Example Rule"
prerequisites:
pp_schedule_item_name: "Release Date"
days_before_or_after: -7
dependent_rules:
- "Dependent Rule 1"
- "Dependent Rule 2"
- condition: "major >= 10"
- schedule_task: "GA for rhel {{ major }}.{{ minor }}"
target_date: "start_date - 7|days"
- rule: "Dependent Rule 1"
- rule: "Dependent Rule 2"
jira_issues:
- template: "examples/jira/main.yaml"
- id: main
template: "examples/jira/main.yaml"
subtasks:
- template: "examples/jira/add_beta_repos.yaml"
- template: "examples/jira/notify_team.yaml"
- template: "examples/jira/secondary.yaml"
- id: add_beta_repos
template: "examples/jira/add_beta_repos.yaml"
- id: notify_team
template: "examples/jira/notify_team.yaml"
- id: secondary
template: "examples/jira/secondary.yaml"

- version: 1
name: "Dependent Rule 1"
prerequisites:
pp_schedule_item_name: "Release Date"
days_before_or_after: -21
- schedule_task: "TASK"
target_date: "start_date - 3|weeks"
jira_issues: []

- version: 1
name: "Dependent Rule 2"
prerequisites:
pp_schedule_item_name: "Release Date"
days_before_or_after: -14
- schedule_task: "TASK"
target_date: "start_date - 2|weeks"
jira_issues: []
89 changes: 88 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ urllib3 = "^2.2.3"
pydantic = "^2.9.2"
pyyaml = "^6.0.1"
atlassian-python-api = "^3.41.16"
Jinja2 = "^3.1.4"

[tool.poetry.group.dev.dependencies]
requests-mock = "^1.12.1"
Expand Down
16 changes: 14 additions & 2 deletions src/retasc/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

from retasc import __doc__ as doc
from retasc import __version__
from retasc.models.generate_schema import generate_schema
from retasc.models.parse_rules import RuleParsingError, parse_rules
from retasc.retasc_logging import init_logging
from retasc.run import run
from retasc.tracing import init_tracing
from retasc.validator.generate_schema import generate_schema
from retasc.validator.parse_rules import RuleParsingError, parse_rules

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -45,6 +46,14 @@ def parse_args():
action="store_true",
)

subparsers.add_parser(
"run", help="Process rules, data from Product Pages and apply changes to Jira"
)
subparsers.add_parser(
"dry-run",
help='Same as "run" but without creating, deleting or modifying any Jira issues',
)

return parser.parse_args()


Expand All @@ -62,4 +71,7 @@ def main():
print("Validation succeeded: The rule files are valid")
elif args.command == "generate-schema":
generate_schema(args.schema_file, output_json=args.json)
elif args.command in ("run", "dry-run"):
dry_run = args.command == "dry-run"
run(dry_run=dry_run)
sys.exit(0)
31 changes: 23 additions & 8 deletions src/retasc/jira_client.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import logging

from atlassian import Jira
from opentelemetry import trace
from requests import Session

logger = logging.getLogger(__name__)
tracer = trace.get_tracer(__name__)


class JiraClient:
"""
Jira Client Wrapper
"""

def __init__(self, api_url: str, token: str | None = None):
def __init__(self, api_url: str, *, token: str, session: Session):
self.api_url = api_url
self.jira = Jira(
url=api_url,
token=token,
session=session,
)

@tracer.start_as_current_span("JiraClient.edit_issue")
def edit_issue(
self, issue_key: str, fields: dict, notify_users: bool = True
) -> None:
Expand All @@ -40,6 +45,7 @@ def edit_issue(
logger.info("Updating Jira issue %r with fields: %r", issue_key, fields)
self.jira.edit_issue(issue_key, fields, notify_users=notify_users)

@tracer.start_as_current_span("JiraClient.create_issue")
def create_issue(
self,
project_key: str,
Expand All @@ -63,23 +69,32 @@ def create_issue(

logger.info("Creating new Jira issue with fields: %r", issue_dict)

issue = self.jira.create_issue(issue_dict)
return issue
data = self.jira.create_issue(issue_dict)
if isinstance(data, dict):
return data

def search_issues(self, jql: str) -> list:
raise RuntimeError(f"Unexpected response: {data!r}")

@tracer.start_as_current_span("JiraClient.search_issues")
def search_issues(self, jql: str, fields: list[str] | None = None) -> list:
"""
Search Issues by JQL
:param jql: string: like "project = DEMO AND status NOT IN (Closed, Resolved) ORDER BY issuekey"
"""

issue_list = self.jira.jql_get_list_of_tickets(jql)
return issue_list
if fields:
return self.jira.jql_get_list_of_tickets(jql, fields=fields)
return self.jira.jql_get_list_of_tickets(jql)

@tracer.start_as_current_span("JiraClient.get_issues")
def get_issue(self, issue_key: str) -> dict:
"""
Get a Jira issue.
"""

issue = self.jira.issue(issue_key)
return issue
data = self.jira.issue(issue_key)
if isinstance(data, dict):
return data

raise RuntimeError(f"Unexpected response: {data}")
Empty file added src/retasc/models/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import yaml

from retasc.validator.models import Rule
from retasc.models.rule import Rule


def _generate_schema(file: TextIO, generator) -> None:
Expand Down
16 changes: 16 additions & 0 deletions src/retasc/models/jira_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from pydantic import BaseModel, Field


class JiraIssueTemplate(BaseModel):
"""Jira issue template with optional sub-tasks."""

id: str = Field(description="Unique identifier for the issue.")
template: str = Field(description="The template file for the jira issue.")
subtasks: list["JiraIssueTemplate"] = Field(
default=[], description="The subtasks for the jira issue."
)

@property
def label(self):
return f"retasc-id-{self.id}"
Loading

0 comments on commit 66e1148

Please sign in to comment.