github-local 0.0.3b4575__tar.gz → 0.0.3b4586__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {github_local-0.0.3b4575/python_packages/github_local.egg-info → github_local-0.0.3b4586}/PKG-INFO +1 -1
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/pyproject.toml +1 -1
- github_local-0.0.3b4586/python_packages/github_local/src/github_failed_job_reply.py +10 -0
- github_local-0.0.3b4586/python_packages/github_local/src/handle_github_failed_job.py +62 -0
- github_local-0.0.3b4586/python_packages/github_local/tests/unit/handle_github_failed_job_unit_test.py +66 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586/python_packages/github_local.egg-info}/PKG-INFO +1 -1
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local.egg-info/SOURCES.txt +4 -1
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/setup.py +1 -1
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/.coveragerc +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/.flake8 +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/README.md +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/pytest.ini +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/conftest.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/__init__.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/__init__.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/classify_github_actions_failure.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/constants_github_local.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/github_actions_failure.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/github_actions_failure_category.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/github_local.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/tests/__init__.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/tests/unit/__init__.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/tests/unit/classify_github_actions_failure_unit_test.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/tests/unit/conftest.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/tests/unit/github_local_unit_test.py +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local.egg-info/dependency_links.txt +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local.egg-info/requires.txt +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local.egg-info/top_level.txt +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/requirements.txt +0 -0
- {github_local-0.0.3b4575 → github_local-0.0.3b4586}/setup.cfg +0 -0
|
@@ -22,7 +22,7 @@ pythonpath = ["."]
|
|
|
22
22
|
name = "github-local"
|
|
23
23
|
# After the b (beta) comes the Jira Work Item ID (sdk-local Makec.ps1 updates it)
|
|
24
24
|
# I believe we are still using the version from setup.py and not from here until Potery will work
|
|
25
|
-
version = "0.0.
|
|
25
|
+
version = "0.0.3b4586" # https://pypi.org/project/github-local/
|
|
26
26
|
description = "github-local Python Package"
|
|
27
27
|
readme = "README.md"
|
|
28
28
|
authors = [
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""A failed GitHub Actions job (GitHub webhook workflow_job event): classify it and tell the team in one Slack message,
|
|
2
|
+
so nobody has to open the log to know whether it is their code, a runner or a missing secret."""
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .classify_github_actions_failure import classify_github_actions_failure, main_error
|
|
7
|
+
from .github_actions_failure import GitHubActionsFailure
|
|
8
|
+
from .github_actions_failure_category import GitHubActionsFailureCategory, is_rerun_safe
|
|
9
|
+
from .github_failed_job_reply import GitHubFailedJobReply
|
|
10
|
+
|
|
11
|
+
WORKFLOW_JOB_EVENT = "workflow_job"
|
|
12
|
+
NEXT_STEP = {
|
|
13
|
+
GitHubActionsFailureCategory.RUNNER: "The runner, not the code: rerun the job (another runner if it repeats).",
|
|
14
|
+
GitHubActionsFailureCategory.CONFIG_SECRET: "A secret / variable / permission is missing or wrong.",
|
|
15
|
+
GitHubActionsFailureCategory.PROCESS_GATE: "A company rule stopped it on purpose - see the error.",
|
|
16
|
+
GitHubActionsFailureCategory.SECURITY_DEPENDENCY: "A dependency or a security alert - update the package.",
|
|
17
|
+
GitHubActionsFailureCategory.TEST_FAILURE: "A test failed - the commit author please check.",
|
|
18
|
+
GitHubActionsFailureCategory.LINT_TYPE: "Lint / type check - the commit author please fix.",
|
|
19
|
+
GitHubActionsFailureCategory.PUBLISH_DEPLOY: "Build and tests passed; publishing or deploying failed.",
|
|
20
|
+
GitHubActionsFailureCategory.INVALID_WORKFLOW: "The workflow file itself is invalid.",
|
|
21
|
+
GitHubActionsFailureCategory.UNKNOWN: "Could not tell from the log.",
|
|
22
|
+
}
|
|
23
|
+
MESSAGE = (":red_circle: *{repo_name}* / {workflow_name} / {job_name} failed - `{category}`\n"
|
|
24
|
+
"{next_step}\n> {main_error}\n{html_url}")
|
|
25
|
+
MAXIMUM_ERROR_LENGTH = 300
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HandleGitHubFailedJob:
|
|
29
|
+
def __init__(self, get_job_log: Callable[[str, int], str]):
|
|
30
|
+
"""get_job_log(repo full name, job id) -> the job log text, e.g. from the GitHub REST API"""
|
|
31
|
+
self.get_job_log = get_job_log
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def is_event(event_name: str, payload: dict[str, Any]) -> bool:
|
|
35
|
+
workflow_job = payload.get("workflow_job") or {}
|
|
36
|
+
is_failed_job = (event_name == WORKFLOW_JOB_EVENT and payload.get("action") == "completed"
|
|
37
|
+
and workflow_job.get("conclusion") == "failure")
|
|
38
|
+
return is_failed_job
|
|
39
|
+
|
|
40
|
+
def handle(self, payload: dict[str, Any]) -> GitHubFailedJobReply:
|
|
41
|
+
workflow_job = payload["workflow_job"]
|
|
42
|
+
repo_full_name = payload["repository"]["full_name"]
|
|
43
|
+
try:
|
|
44
|
+
log = self.get_job_log(repo_full_name, workflow_job["id"])
|
|
45
|
+
except Exception:
|
|
46
|
+
# The message still goes out: the category is then UNKNOWN
|
|
47
|
+
log = ""
|
|
48
|
+
failed_step_name = next((step.get("name") for step in workflow_job.get("steps") or []
|
|
49
|
+
if step.get("conclusion") == "failure"), None)
|
|
50
|
+
failure = GitHubActionsFailure(repo_name=payload["repository"]["name"],
|
|
51
|
+
workflow_name=workflow_job.get("workflow_name") or "",
|
|
52
|
+
job_name=workflow_job.get("name"), failed_step_name=failed_step_name,
|
|
53
|
+
runner_name=workflow_job.get("runner_name"), log=log)
|
|
54
|
+
category = classify_github_actions_failure(failure)
|
|
55
|
+
error = main_error(log)[:MAXIMUM_ERROR_LENGTH] or f"Failed step: {failed_step_name or 'unknown'}"
|
|
56
|
+
channel_message = MESSAGE.format(repo_name=failure.repo_name, workflow_name=failure.workflow_name,
|
|
57
|
+
job_name=failure.job_name, category=category.value,
|
|
58
|
+
next_step=NEXT_STEP[category], main_error=error,
|
|
59
|
+
html_url=workflow_job.get("html_url", ""))
|
|
60
|
+
reply = GitHubFailedJobReply(channel_message=channel_message, category=category,
|
|
61
|
+
is_rerun_safe=is_rerun_safe(category))
|
|
62
|
+
return reply
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from github_local.src.github_actions_failure_category import GitHubActionsFailureCategory
|
|
4
|
+
from github_local.src.handle_github_failed_job import HandleGitHubFailedJob
|
|
5
|
+
|
|
6
|
+
JOB_URL = "https://github.com/circles-zone/sdk-local/actions/runs/1/job/2"
|
|
7
|
+
RUNNER_LOG = "Run tests\n##[error]No space left on device\n##[error]Process completed with exit code 1."
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def workflow_job_payload(conclusion: str = "failure", action: str = "completed") -> dict:
|
|
11
|
+
payload = {"action": action,
|
|
12
|
+
"repository": {"name": "sdk-local", "full_name": "circles-zone/sdk-local"},
|
|
13
|
+
"workflow_job": {"id": 2, "conclusion": conclusion, "name": "build", "workflow_name": "Play1",
|
|
14
|
+
"runner_name": "runner-1", "html_url": JOB_URL,
|
|
15
|
+
"steps": [{"name": "Checkout", "conclusion": "success"},
|
|
16
|
+
{"name": "Run tests", "conclusion": "failure"}]}}
|
|
17
|
+
return payload
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize("event_name, payload, is_event", [
|
|
21
|
+
("workflow_job", workflow_job_payload(), True),
|
|
22
|
+
("workflow_job", workflow_job_payload(conclusion="success"), False),
|
|
23
|
+
("workflow_job", workflow_job_payload(action="in_progress"), False),
|
|
24
|
+
("push", workflow_job_payload(), False),
|
|
25
|
+
("workflow_job", {"action": "completed"}, False),
|
|
26
|
+
])
|
|
27
|
+
def test_is_event_is_a_completed_failed_job(event_name, payload, is_event):
|
|
28
|
+
assert HandleGitHubFailedJob.is_event(event_name, payload) is is_event
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_a_runner_failure_says_to_rerun_and_links_the_job():
|
|
32
|
+
job_logs: list[tuple[str, int]] = []
|
|
33
|
+
|
|
34
|
+
def get_job_log(repo_full_name: str, job_id: int) -> str:
|
|
35
|
+
job_logs.append((repo_full_name, job_id))
|
|
36
|
+
return RUNNER_LOG
|
|
37
|
+
|
|
38
|
+
reply = HandleGitHubFailedJob(get_job_log).handle(workflow_job_payload())
|
|
39
|
+
|
|
40
|
+
assert job_logs == [("circles-zone/sdk-local", 2)]
|
|
41
|
+
assert reply.category == GitHubActionsFailureCategory.RUNNER
|
|
42
|
+
assert reply.is_rerun_safe
|
|
43
|
+
assert reply.channel_message.startswith(":red_circle: *sdk-local* / Play1 / build failed - `runner`\n")
|
|
44
|
+
assert "rerun the job" in reply.channel_message
|
|
45
|
+
assert "> No space left on device" in reply.channel_message
|
|
46
|
+
assert reply.channel_message.endswith(JOB_URL)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_without_a_log_the_message_names_the_failed_step():
|
|
50
|
+
def get_job_log(repo_full_name: str, job_id: int) -> str:
|
|
51
|
+
raise RuntimeError("Not Found")
|
|
52
|
+
|
|
53
|
+
reply = HandleGitHubFailedJob(get_job_log).handle(workflow_job_payload())
|
|
54
|
+
|
|
55
|
+
assert reply.category == GitHubActionsFailureCategory.UNKNOWN
|
|
56
|
+
assert not reply.is_rerun_safe
|
|
57
|
+
assert "> Failed step: Run tests" in reply.channel_message
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_a_job_without_steps_still_gets_a_message():
|
|
61
|
+
payload = workflow_job_payload()
|
|
62
|
+
del payload["workflow_job"]["steps"]
|
|
63
|
+
|
|
64
|
+
reply = HandleGitHubFailedJob(lambda repo_full_name, job_id: "").handle(payload)
|
|
65
|
+
|
|
66
|
+
assert "> Failed step: unknown" in reply.channel_message
|
|
@@ -17,9 +17,12 @@ python_packages/github_local/src/classify_github_actions_failure.py
|
|
|
17
17
|
python_packages/github_local/src/constants_github_local.py
|
|
18
18
|
python_packages/github_local/src/github_actions_failure.py
|
|
19
19
|
python_packages/github_local/src/github_actions_failure_category.py
|
|
20
|
+
python_packages/github_local/src/github_failed_job_reply.py
|
|
20
21
|
python_packages/github_local/src/github_local.py
|
|
22
|
+
python_packages/github_local/src/handle_github_failed_job.py
|
|
21
23
|
python_packages/github_local/tests/__init__.py
|
|
22
24
|
python_packages/github_local/tests/unit/__init__.py
|
|
23
25
|
python_packages/github_local/tests/unit/classify_github_actions_failure_unit_test.py
|
|
24
26
|
python_packages/github_local/tests/unit/conftest.py
|
|
25
|
-
python_packages/github_local/tests/unit/github_local_unit_test.py
|
|
27
|
+
python_packages/github_local/tests/unit/github_local_unit_test.py
|
|
28
|
+
python_packages/github_local/tests/unit/handle_github_failed_job_unit_test.py
|
|
@@ -32,7 +32,7 @@ setuptools.setup(
|
|
|
32
32
|
# Updating the b{{JiraWorkItemId}} done automatically by makec.ps1 / make_py.ps1
|
|
33
33
|
# According to GitFlow, in the Feature Branch, after the letter 'b' comes the
|
|
34
34
|
# Jira Work Item ID, i.e., 0.0.1b2432. Semantic Version simver.org
|
|
35
|
-
version='0.0.
|
|
35
|
+
version='0.0.3b4586', # https://pypi.org/project/github-local#History
|
|
36
36
|
author="Circles",
|
|
37
37
|
author_email="info@circlez.ai",
|
|
38
38
|
description=f"{PACKAGE_DESCRIPTION}",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/__init__.py
RENAMED
|
File without changes
|
{github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/src/github_local.py
RENAMED
|
File without changes
|
{github_local-0.0.3b4575 → github_local-0.0.3b4586}/python_packages/github_local/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|