Loading .gitlab-ci.yml +2 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,8 @@ include: - component: "$CI_SERVER_FQDN/$TBC_NAMESPACE/python/gitlab-ci-python@6" inputs: image: "registry.hub.docker.com/library/python:3.12" mypy-enabled: true mypy-files: gitlab_butler ruff-enabled: true package-enabled: true release-enabled: true Loading gitlab_butler/butler.py +16 −13 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ class PipelineSource: # for merge-requests self.state = state def __repr__(self): def __repr__(self) -> str: return f'PipelineSource(existing={self.existing}, source_type={self.source_type}, protected={self.protected}, default={self.default}, state={self.state})' Loading @@ -51,14 +51,14 @@ class Butler: client: Gitlab, group_path: str, exclude: Optional[list[str]] = None, skip_subgroups=False, dry_run=True, verbose=False, debug=False, continue_on_error=False, delay_between_api_call=0, pipeline_deletion_limit=0, default_cfg=ButlerCfg(), skip_subgroups: bool = False, dry_run: bool = True, verbose: bool = False, debug: bool = False, continue_on_error: bool = False, delay_between_api_call: int = 0, pipeline_deletion_limit: int = 0, default_cfg: ButlerCfg = ButlerCfg(), ) -> None: self.client = client self.group_path = group_path Loading Loading @@ -185,7 +185,7 @@ class Butler: case PipelineSourceEnum.MERGE_REQUEST: # check if mr status match one pattern for state in cfg.pipelines.keep.per_merge_request.by_status.keys(): if fnmatch(source.state, state): if fnmatch(source.state or "", state): if self.verbose and self.debug: print(f'Pipeline ref {pipeline.ref} matching MR state {state} => {cfg.pipelines.keep.per_merge_request.by_status[state]}') return cfg.pipelines.keep.per_merge_request.by_status[state] Loading Loading @@ -293,7 +293,7 @@ class Butler: return PipelineSource(existing=False) def delete_pipeline(self, project: Project, pipeline: ProjectPipeline): def delete_pipeline(self, project: Project, pipeline: ProjectPipeline) -> None: """ Delete a pipeline :param pipeline: Loading Loading @@ -361,6 +361,9 @@ class Butler: # iterate over project pipelines (starting with oldest) for pipeline in project.pipelines.list(iterator=True, sort="asc", order_by="updated_at"): # list() methods in python-gitlab are not narrowly typed, so let's help mypy assert isinstance(pipeline, ProjectPipeline) # if the pipeline has been updated after the cutoff date: keep and break loop if datetime.strptime(pipeline.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ") >= updated_limit: if self.verbose and self.debug: Loading Loading @@ -462,7 +465,7 @@ class Butler: print( f"🏢 Group {AnsiColors.BLUE}{subgroup.full_path}{AnsiColors.RESET} matches excludes: {AnsiColors.HGRAY}skip{AnsiColors.RESET}" ) else: self.clean_group(self.client.groups.get(subgroup.get_id())) elif (subgroup_id := subgroup.get_id()): self.clean_group(self.client.groups.get(subgroup_id)) else: print("- skipping sub groups...") gitlab_butler/main.py +5 −9 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ def to_url(api_url: str) -> str: return api_url[0:-7] if api_url.endswith("/api/v4") else api_url def generate_parser(): def generate_parser() -> argparse.ArgumentParser: # define command parser parser = argparse.ArgumentParser( prog="gitlab-butler", Loading Loading @@ -48,7 +48,7 @@ def generate_parser(): parser.add_argument( "--exclude", action="append", default=os.getenv("EXCLUDE").split(",") if os.getenv("EXCLUDE") else [], default=x.split(",") if (x := os.getenv("EXCLUDE")) else [], help="project/group path to exclude from processing (relative to --group-path)", ) parser.add_argument( Loading Loading @@ -91,18 +91,14 @@ def generate_parser(): "--pipelines-keep-per-branch", type=str, action="append", default=os.getenv("PIPELINES_KEEP_PER_BRANCH").split(" ") if os.getenv("PIPELINES_KEEP_PER_BRANCH") else [], default=x.split(" ") if (x := os.getenv("PIPELINES_KEEP_PER_BRANCH")) else [], help="number of pipelines to keep per branch (⚠ branch MUST still exist)", ) parser.add_argument( "--pipelines-keep-per-tag", type=str, action="append", default=os.getenv("PIPELINES_KEEP_PER_TAG").split(" ") if os.getenv("PIPELINES_KEEP_PER_TAG") else [], default=x.split(" ") if (x := os.getenv("PIPELINES_KEEP_PER_TAG")) else [], help="number of pipelines to keep per tag (⚠ tag MUST still exist)", ) parser.add_argument( Loading @@ -120,7 +116,7 @@ def generate_parser(): return parser def generate_cfg(args): def generate_cfg(args: argparse.Namespace) -> ButlerCfg: # build default config cfg = ButlerCfg() cfg.pipelines.delete_older_than = args.pipelines_delete_older_than Loading poetry.lock +13 −2 Original line number Diff line number Diff line # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" Loading Loading @@ -702,6 +702,17 @@ files = [ {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, ] [[package]] name = "types-pyyaml" version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, ] [[package]] name = "typing-extensions" version = "4.12.2" Loading Loading @@ -733,4 +744,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.11" content-hash = "fd70685ec62161f7c4556ccb8dbc863b5f1930e9a3d3f30b12ed83d50d74999d" content-hash = "1fd29940d3fd2d59580bf373be062fee1608218bdcbd292150913fd2339f2938" pyproject.toml +1 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ python = "^3.11" python-gitlab = "^5.0.0" pydantic = "^2.7.4" pyyaml = "^6.0.1" types-pyyaml = "^6.0.12.20240917" [tool.poetry.group.dev.dependencies] # new development dependencies can be added with 'poetry add -D yyy' Loading Loading
.gitlab-ci.yml +2 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,8 @@ include: - component: "$CI_SERVER_FQDN/$TBC_NAMESPACE/python/gitlab-ci-python@6" inputs: image: "registry.hub.docker.com/library/python:3.12" mypy-enabled: true mypy-files: gitlab_butler ruff-enabled: true package-enabled: true release-enabled: true Loading
gitlab_butler/butler.py +16 −13 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ class PipelineSource: # for merge-requests self.state = state def __repr__(self): def __repr__(self) -> str: return f'PipelineSource(existing={self.existing}, source_type={self.source_type}, protected={self.protected}, default={self.default}, state={self.state})' Loading @@ -51,14 +51,14 @@ class Butler: client: Gitlab, group_path: str, exclude: Optional[list[str]] = None, skip_subgroups=False, dry_run=True, verbose=False, debug=False, continue_on_error=False, delay_between_api_call=0, pipeline_deletion_limit=0, default_cfg=ButlerCfg(), skip_subgroups: bool = False, dry_run: bool = True, verbose: bool = False, debug: bool = False, continue_on_error: bool = False, delay_between_api_call: int = 0, pipeline_deletion_limit: int = 0, default_cfg: ButlerCfg = ButlerCfg(), ) -> None: self.client = client self.group_path = group_path Loading Loading @@ -185,7 +185,7 @@ class Butler: case PipelineSourceEnum.MERGE_REQUEST: # check if mr status match one pattern for state in cfg.pipelines.keep.per_merge_request.by_status.keys(): if fnmatch(source.state, state): if fnmatch(source.state or "", state): if self.verbose and self.debug: print(f'Pipeline ref {pipeline.ref} matching MR state {state} => {cfg.pipelines.keep.per_merge_request.by_status[state]}') return cfg.pipelines.keep.per_merge_request.by_status[state] Loading Loading @@ -293,7 +293,7 @@ class Butler: return PipelineSource(existing=False) def delete_pipeline(self, project: Project, pipeline: ProjectPipeline): def delete_pipeline(self, project: Project, pipeline: ProjectPipeline) -> None: """ Delete a pipeline :param pipeline: Loading Loading @@ -361,6 +361,9 @@ class Butler: # iterate over project pipelines (starting with oldest) for pipeline in project.pipelines.list(iterator=True, sort="asc", order_by="updated_at"): # list() methods in python-gitlab are not narrowly typed, so let's help mypy assert isinstance(pipeline, ProjectPipeline) # if the pipeline has been updated after the cutoff date: keep and break loop if datetime.strptime(pipeline.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ") >= updated_limit: if self.verbose and self.debug: Loading Loading @@ -462,7 +465,7 @@ class Butler: print( f"🏢 Group {AnsiColors.BLUE}{subgroup.full_path}{AnsiColors.RESET} matches excludes: {AnsiColors.HGRAY}skip{AnsiColors.RESET}" ) else: self.clean_group(self.client.groups.get(subgroup.get_id())) elif (subgroup_id := subgroup.get_id()): self.clean_group(self.client.groups.get(subgroup_id)) else: print("- skipping sub groups...")
gitlab_butler/main.py +5 −9 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ def to_url(api_url: str) -> str: return api_url[0:-7] if api_url.endswith("/api/v4") else api_url def generate_parser(): def generate_parser() -> argparse.ArgumentParser: # define command parser parser = argparse.ArgumentParser( prog="gitlab-butler", Loading Loading @@ -48,7 +48,7 @@ def generate_parser(): parser.add_argument( "--exclude", action="append", default=os.getenv("EXCLUDE").split(",") if os.getenv("EXCLUDE") else [], default=x.split(",") if (x := os.getenv("EXCLUDE")) else [], help="project/group path to exclude from processing (relative to --group-path)", ) parser.add_argument( Loading Loading @@ -91,18 +91,14 @@ def generate_parser(): "--pipelines-keep-per-branch", type=str, action="append", default=os.getenv("PIPELINES_KEEP_PER_BRANCH").split(" ") if os.getenv("PIPELINES_KEEP_PER_BRANCH") else [], default=x.split(" ") if (x := os.getenv("PIPELINES_KEEP_PER_BRANCH")) else [], help="number of pipelines to keep per branch (⚠ branch MUST still exist)", ) parser.add_argument( "--pipelines-keep-per-tag", type=str, action="append", default=os.getenv("PIPELINES_KEEP_PER_TAG").split(" ") if os.getenv("PIPELINES_KEEP_PER_TAG") else [], default=x.split(" ") if (x := os.getenv("PIPELINES_KEEP_PER_TAG")) else [], help="number of pipelines to keep per tag (⚠ tag MUST still exist)", ) parser.add_argument( Loading @@ -120,7 +116,7 @@ def generate_parser(): return parser def generate_cfg(args): def generate_cfg(args: argparse.Namespace) -> ButlerCfg: # build default config cfg = ButlerCfg() cfg.pipelines.delete_older_than = args.pipelines_delete_older_than Loading
poetry.lock +13 −2 Original line number Diff line number Diff line # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" Loading Loading @@ -702,6 +702,17 @@ files = [ {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, ] [[package]] name = "types-pyyaml" version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, ] [[package]] name = "typing-extensions" version = "4.12.2" Loading Loading @@ -733,4 +744,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.11" content-hash = "fd70685ec62161f7c4556ccb8dbc863b5f1930e9a3d3f30b12ed83d50d74999d" content-hash = "1fd29940d3fd2d59580bf373be062fee1608218bdcbd292150913fd2339f2938"
pyproject.toml +1 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ python = "^3.11" python-gitlab = "^5.0.0" pydantic = "^2.7.4" pyyaml = "^6.0.1" types-pyyaml = "^6.0.12.20240917" [tool.poetry.group.dev.dependencies] # new development dependencies can be added with 'poetry add -D yyy' Loading