ClickHouse/ci/praktika/parser.py

259 lines
11 KiB
Python
Raw Normal View History

2024-10-01 19:19:35 +00:00
import dataclasses
from typing import Any, Dict, List
from praktika import Artifact, Workflow
from praktika.mangle import _get_workflows
class AddonType:
PY = "py"
@dataclasses.dataclass
class WorkflowYaml:
@dataclasses.dataclass
class JobYaml:
name: str
needs: List[str]
runs_on: List[str]
artifacts_gh_requires: List["WorkflowYaml.ArtifactYaml"]
artifacts_gh_provides: List["WorkflowYaml.ArtifactYaml"]
addons: List["WorkflowYaml.JobAddonYaml"]
gh_app_auth: bool
run_unless_cancelled: bool
parameter: Any
def __repr__(self):
return self.name
@dataclasses.dataclass
class ArtifactYaml:
name: str
provided_by: str
required_by: List[str]
path: str
type: str
def __repr__(self):
return self.name
@dataclasses.dataclass
class JobAddonYaml:
install_python: bool
requirements_txt_path: str
name: str
event: str
branches: List[str]
jobs: List[JobYaml]
job_to_config: Dict[str, JobYaml]
artifact_to_config: Dict[str, ArtifactYaml]
secret_names_gh: List[str]
enable_cache: bool
class WorkflowConfigParser:
def __init__(self, config: Workflow.Config):
self.workflow_name = config.name
self.config = config
self.requires_all = [] # type: List[str]
self.provides_all = [] # type: List[str]
self.job_names_all = [] # type: List[str]
self.artifact_to_providing_job_map = {} # type: Dict[str, List[str]]
self.artifact_to_job_requires_map = {} # type: Dict[str, List[str]]
self.artifact_map = {} # type: Dict[str, List[Artifact.Config]]
self.job_to_provides_artifacts = {} # type: Dict[str, List[Artifact.Config]]
self.job_to_requires_artifacts = {} # type: Dict[str, List[Artifact.Config]]
self.workflow_yaml_config = WorkflowYaml(
name=self.workflow_name,
event=config.event,
branches=[],
jobs=[],
secret_names_gh=[],
job_to_config={},
artifact_to_config={},
enable_cache=False,
)
def parse(self):
self.workflow_yaml_config.enable_cache = self.config.enable_cache
# populate WorkflowYaml.branches
if self.config.event in (Workflow.Event.PUSH,):
assert (
self.config.branches
), f'Workflow.Config.branches (e.g. ["main"]) must be set for workflow with event [{self.config.event}], workflow [{self.workflow_name}]'
assert (
not self.config.base_branches
), f'Workflow.Config.base_branches (e.g. ["main"]) must not be set for workflow with event [{self.config.event}], workflow [{self.workflow_name}]'
assert isinstance(
self.config.branches, list
), f'Workflow.Config.branches must be of type list (e.g. ["main"]), workflow [{self.workflow_name}]'
self.workflow_yaml_config.branches = self.config.branches
elif self.config.event in (Workflow.Event.PULL_REQUEST,):
assert (
self.config.base_branches
), f'Workflow.Config.base_branches (e.g. ["main"]) must be set for workflow with event [{self.config.event}], workflow [{self.workflow_name}]'
assert (
not self.config.branches
), f'Workflow.Config.branches (e.g. ["main"]) must not be set for workflow with event [{self.config.event}], workflow [{self.workflow_name}]'
assert isinstance(
self.config.base_branches, list
), f'Workflow.Config.base_branches must be of type list (e.g. ["main"]), workflow [{self.workflow_name}]'
self.workflow_yaml_config.branches = self.config.base_branches
# populate WorkflowYaml.artifact_to_config with phony artifacts
for job in self.config.jobs:
assert (
job.name not in self.workflow_yaml_config.artifact_to_config
), f"Not uniq Job name [{job.name}], workflow [{self.workflow_name}]"
2024-10-23 20:14:22 +00:00
self.workflow_yaml_config.artifact_to_config[job.name] = (
WorkflowYaml.ArtifactYaml(
name=job.name,
provided_by=job.name,
required_by=[],
path="",
type=Artifact.Type.PHONY,
)
2024-10-01 19:19:35 +00:00
)
# populate jobs
for job in self.config.jobs:
job_yaml_config = WorkflowYaml.JobYaml(
name=job.name,
addons=[],
artifacts_gh_requires=[],
artifacts_gh_provides=[],
needs=[],
runs_on=[],
gh_app_auth=False,
run_unless_cancelled=job.run_unless_cancelled,
parameter=None,
)
self.workflow_yaml_config.jobs.append(job_yaml_config)
assert (
job.name not in self.workflow_yaml_config.job_to_config
), f"Job name [{job.name}] is not uniq, workflow [{self.workflow_name}]"
self.workflow_yaml_config.job_to_config[job.name] = job_yaml_config
# populate WorkflowYaml.artifact_to_config
if self.config.artifacts:
for artifact in self.config.artifacts:
assert (
artifact.name not in self.workflow_yaml_config.artifact_to_config
), f"Artifact name [{artifact.name}] is not uniq, workflow [{self.workflow_name}]"
artifact_yaml_config = WorkflowYaml.ArtifactYaml(
name=artifact.name,
provided_by="",
required_by=[],
path=artifact.path,
type=artifact.type,
)
2024-10-23 20:14:22 +00:00
self.workflow_yaml_config.artifact_to_config[artifact.name] = (
artifact_yaml_config
)
2024-10-01 19:19:35 +00:00
# populate ArtifactYaml.provided_by
for job in self.config.jobs:
if job.provides:
for artifact_name in job.provides:
assert (
artifact_name in self.workflow_yaml_config.artifact_to_config
), f"Artifact [{artifact_name}] has no config, job [{job.name}], workflow [{self.workflow_name}]"
assert not self.workflow_yaml_config.artifact_to_config[
artifact_name
].provided_by, f"Artifact [{artifact_name}] provided by multiple jobs [{self.workflow_yaml_config.artifact_to_config[artifact_name].provided_by}] and [{job.name}]"
self.workflow_yaml_config.artifact_to_config[
artifact_name
].provided_by = job.name
# populate ArtifactYaml.required_by
for job in self.config.jobs:
if job.requires:
for artifact_name in job.requires:
assert (
artifact_name in self.workflow_yaml_config.artifact_to_config
), f"Artifact [{artifact_name}] has no config, job [{job.name}], workflow [{self.workflow_name}]"
assert self.workflow_yaml_config.artifact_to_config[
artifact_name
].provided_by, f"Artifact [{artifact_name}] has no job providing it, required by job [{job.name}], workflow [{self.workflow_name}]"
self.workflow_yaml_config.artifact_to_config[
artifact_name
].required_by.append(job.name)
# populate JobYaml.addons
for job in self.config.jobs:
if job.job_requirements:
addon_yaml = WorkflowYaml.JobAddonYaml(
requirements_txt_path=job.job_requirements.python_requirements_txt,
install_python=job.job_requirements.python,
)
self.workflow_yaml_config.job_to_config[job.name].addons.append(
addon_yaml
)
if self.config.enable_report:
for job in self.config.jobs:
# auth required for every job with enabled HTML, so that workflow summary status can be updated
self.workflow_yaml_config.job_to_config[job.name].gh_app_auth = True
# populate JobYaml.runs_on
for job in self.config.jobs:
self.workflow_yaml_config.job_to_config[job.name].runs_on = job.runs_on
# populate JobYaml.artifacts_gh_requires, JobYaml.artifacts_gh_provides and JobYaml.needs
for (
artifact_name,
artifact,
) in self.workflow_yaml_config.artifact_to_config.items():
# assert (
# artifact.provided_by
# and artifact.provided_by in self.workflow_yaml_config.job_to_config
# ), f"Artifact [{artifact_name}] has no valid job providing it [{artifact.provided_by}]"
for job_name in artifact.required_by:
if (
artifact.provided_by
not in self.workflow_yaml_config.job_to_config[job_name].needs
):
self.workflow_yaml_config.job_to_config[job_name].needs.append(
artifact.provided_by
)
if artifact.type in (Artifact.Type.GH,):
self.workflow_yaml_config.job_to_config[
job_name
].artifacts_gh_requires.append(artifact)
elif artifact.type in (Artifact.Type.PHONY, Artifact.Type.S3):
pass
else:
assert (
False
), f"Artifact [{artifact_name}] has unsupported type [{artifact.type}]"
if not artifact.required_by and artifact.type != Artifact.Type.PHONY:
print(
f"WARNING: Artifact [{artifact_name}] provided by job [{artifact.provided_by}] not required by any job in workflow [{self.workflow_name}]"
)
if artifact.type == Artifact.Type.GH:
self.workflow_yaml_config.job_to_config[
artifact.provided_by
].artifacts_gh_provides.append(artifact)
# populate JobYaml.parametrize
for job in self.config.jobs:
self.workflow_yaml_config.job_to_config[job.name].parameter = job.parameter
# populate secrets
for secret_config in self.config.secrets:
if secret_config.is_gh():
self.workflow_yaml_config.secret_names_gh.append(secret_config.name)
return self
if __name__ == "__main__":
# test
workflows = _get_workflows()
for workflow in workflows:
WorkflowConfigParser(workflow).parse()