gitlabform 5.1.1__tar.gz → 5.3.0__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.
Files changed (85) hide show
  1. {gitlabform-5.1.1 → gitlabform-5.3.0}/PKG-INFO +7 -7
  2. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/__init__.py +85 -114
  3. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/configuration/core.py +23 -28
  4. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/configuration/transform.py +10 -10
  5. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/__init__.py +1 -0
  6. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/core.py +3 -4
  7. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/project_protected_environments.py +2 -2
  8. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/python_gitlab.py +2 -2
  9. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/lists/__init__.py +1 -0
  10. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/lists/filter.py +4 -5
  11. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/lists/groups.py +4 -5
  12. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/lists/projects.py +52 -24
  13. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/output.py +7 -12
  14. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/__init__.py +3 -4
  15. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/abstract_processor.py +9 -9
  16. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_members_processor.py +19 -18
  17. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_saml_links_processor.py +1 -2
  18. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_variables_processor.py +2 -2
  19. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/multiple_entities_processor.py +17 -18
  20. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/branches_processor.py +25 -29
  21. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/files_processor.py +15 -17
  22. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/integrations_processor.py +5 -5
  23. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/job_token_scope_processor.py +1 -1
  24. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/members_processor.py +22 -22
  25. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/merge_requests_approvals.py +11 -8
  26. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/project_processor.py +7 -7
  27. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/project_security_settings.py +1 -3
  28. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/project_variables_processor.py +7 -8
  29. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/remote_mirrors_processor.py +14 -16
  30. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/resource_groups_processor.py +2 -4
  31. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/tags_processor.py +6 -8
  32. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/util/difference_logger.py +2 -2
  33. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/util/labels_processor.py +4 -5
  34. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/util/variables_processor.py +8 -9
  35. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform.egg-info/PKG-INFO +7 -7
  36. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform.egg-info/requires.txt +6 -6
  37. {gitlabform-5.1.1 → gitlabform-5.3.0}/pyproject.toml +13 -12
  38. {gitlabform-5.1.1 → gitlabform-5.3.0}/LICENSE +0 -0
  39. {gitlabform-5.1.1 → gitlabform-5.3.0}/README.md +0 -0
  40. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/configuration/__init__.py +0 -0
  41. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/configuration/common.py +0 -0
  42. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/configuration/groups.py +0 -0
  43. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/configuration/projects.py +0 -0
  44. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/constants.py +0 -0
  45. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/commits.py +0 -0
  46. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/group_badges.py +0 -0
  47. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/group_ldap_links.py +0 -0
  48. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/groups.py +0 -0
  49. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/merge_requests.py +0 -0
  50. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/pipelines.py +0 -0
  51. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/project_badges.py +0 -0
  52. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/project_deploy_keys.py +0 -0
  53. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
  54. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/projects.py +0 -0
  55. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/gitlab/variables.py +0 -0
  56. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/application/__init__.py +0 -0
  57. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/application/application_settings_processor.py +0 -0
  58. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/defining_keys.py +0 -0
  59. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/__init__.py +0 -0
  60. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_badges_processor.py +0 -0
  61. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_hooks_processor.py +0 -0
  62. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_labels_processor.py +0 -0
  63. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
  64. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
  65. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/group/group_settings_processor.py +0 -0
  66. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/__init__.py +0 -0
  67. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/badges_processor.py +0 -0
  68. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
  69. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/hooks_processor.py +0 -0
  70. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
  71. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/project_labels_processor.py +0 -0
  72. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
  73. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/project_settings_processor.py +0 -0
  74. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/project/schedules_processor.py +0 -0
  75. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/shared/__init__.py +0 -0
  76. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
  77. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/util/__init__.py +0 -0
  78. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/processors/util/decorators.py +0 -0
  79. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/run.py +0 -0
  80. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform/util.py +0 -0
  81. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform.egg-info/SOURCES.txt +0 -0
  82. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform.egg-info/dependency_links.txt +0 -0
  83. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform.egg-info/entry_points.txt +0 -0
  84. {gitlabform-5.1.1 → gitlabform-5.3.0}/gitlabform.egg-info/top_level.txt +0 -0
  85. {gitlabform-5.1.1 → gitlabform-5.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlabform
3
- Version: 5.1.1
3
+ Version: 5.3.0
4
4
  Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
5
5
  Author: Greg Dubicki and Contributors
6
6
  Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
@@ -21,8 +21,6 @@ Requires-Python: >=3.12.0
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: certifi==2026.2.25
24
- Requires-Dist: cli-ui==0.19.0
25
- Requires-Dist: ez-yaml==1.2.0
26
24
  Requires-Dist: Jinja2==3.1.6
27
25
  Requires-Dist: luddite==1.0.4
28
26
  Requires-Dist: MarkupSafe==3.0.3
@@ -31,19 +29,21 @@ Requires-Dist: packaging==26.0
31
29
  Requires-Dist: python-gitlab==8.2.0
32
30
  Requires-Dist: python-gitlab[graphql]==8.2.0
33
31
  Requires-Dist: requests==2.33.1
32
+ Requires-Dist: rich==15.0.0
34
33
  Requires-Dist: ruamel.yaml==0.17.21
35
34
  Requires-Dist: yamlpath==3.8.2
35
+ Requires-Dist: ez-yaml==1.2.0
36
36
  Provides-Extra: test
37
37
  Requires-Dist: coverage==7.13.5; extra == "test"
38
- Requires-Dist: cryptography==46.0.6; extra == "test"
38
+ Requires-Dist: cryptography==46.0.7; extra == "test"
39
39
  Requires-Dist: deepdiff==9.0.0; extra == "test"
40
- Requires-Dist: mypy==1.20.0; extra == "test"
40
+ Requires-Dist: mypy==1.20.1; extra == "test"
41
41
  Requires-Dist: mypy-extensions==1.1.0; extra == "test"
42
42
  Requires-Dist: pre-commit==2.21.0; extra == "test"
43
- Requires-Dist: pytest==9.0.2; extra == "test"
43
+ Requires-Dist: pytest==9.0.3; extra == "test"
44
44
  Requires-Dist: pytest-cov==7.1.0; extra == "test"
45
45
  Requires-Dist: pytest-rerunfailures==16.1; extra == "test"
46
- Requires-Dist: types-requests==2.33.0.20260402; extra == "test"
46
+ Requires-Dist: types-requests==2.33.0.20260408; extra == "test"
47
47
  Requires-Dist: types-setuptools==82.0.0.20260402; extra == "test"
48
48
  Requires-Dist: xkcdpass==1.30.0; extra == "test"
49
49
  Provides-Extra: docs
@@ -1,30 +1,17 @@
1
1
  import sys
2
- from logging import debug
2
+ from logging import debug, critical, error, warning, info
3
+
4
+ # Use Rich to make logs have a colorized and formatted output
5
+ from rich.logging import RichHandler
6
+ from rich.console import Console
3
7
 
4
8
  import argparse
5
- import cli_ui
6
9
  import logging
7
10
  import luddite
8
11
  from importlib.metadata import version as package_version
9
12
  import textwrap
10
13
  import traceback
11
- from cli_ui import (
12
- Symbol,
13
- reset,
14
- blue,
15
- message,
16
- error,
17
- info,
18
- fatal,
19
- info_1,
20
- debug as verbose,
21
- red,
22
- green,
23
- yellow,
24
- Token,
25
- purple,
26
- warning,
27
- )
14
+
28
15
  from packaging import version
29
16
  from typing import Any, Tuple
30
17
 
@@ -46,11 +33,14 @@ from gitlabform.processors.application import ApplicationProcessors
46
33
  from gitlabform.processors.group import GroupProcessors
47
34
  from gitlabform.processors.project import ProjectProcessors
48
35
 
36
+ console = Console()
37
+
49
38
 
50
39
  class GitLabForm:
51
40
  def __init__(
52
41
  self,
53
42
  include_archived_projects=True,
43
+ include_projects_scheduled_for_deletion=True,
54
44
  target=None,
55
45
  config_string=None,
56
46
  noop=False,
@@ -72,6 +62,7 @@ class GitLabForm:
72
62
  self.output_file = output_file
73
63
  self.skip_version_check = True
74
64
  self.include_archived_projects = include_archived_projects
65
+ self.include_projects_scheduled_for_deletion = include_projects_scheduled_for_deletion
75
66
  self.just_show_version = False
76
67
  self.terminate_after_error = True
77
68
  self.only_sections = "all"
@@ -95,6 +86,7 @@ class GitLabForm:
95
86
  self.output_file,
96
87
  self.skip_version_check,
97
88
  self.include_archived_projects,
89
+ self.include_projects_scheduled_for_deletion,
98
90
  self.just_show_version,
99
91
  self.terminate_after_error,
100
92
  self.only_sections,
@@ -109,10 +101,8 @@ class GitLabForm:
109
101
  sys.exit(0)
110
102
 
111
103
  if not self.target:
112
- fatal(
113
- "target parameter is required.",
114
- exit_code=EXIT_INVALID_INPUT,
115
- )
104
+ critical("target parameter is required.")
105
+ sys.exit(EXIT_INVALID_INPUT)
116
106
 
117
107
  self.gitlab, self.configuration = self._initialize_configuration_and_gitlab()
118
108
 
@@ -128,6 +118,7 @@ class GitLabForm:
128
118
  self.gitlab,
129
119
  self.configuration,
130
120
  self.include_archived_projects,
121
+ self.include_projects_scheduled_for_deletion,
131
122
  self.recurse_subgroups,
132
123
  )
133
124
 
@@ -236,6 +227,13 @@ class GitLabForm:
236
227
  help="Includes processing projects that are archived",
237
228
  )
238
229
 
230
+ parser.add_argument(
231
+ "--include-projects-scheduled-for-deletion",
232
+ dest="include_projects_scheduled_for_deletion",
233
+ action="store_true",
234
+ help="Includes processing projects that are scheduled for deletion",
235
+ )
236
+
239
237
  parser.add_argument(
240
238
  "-t",
241
239
  "--terminate",
@@ -317,6 +315,7 @@ class GitLabForm:
317
315
  args.output_file,
318
316
  args.skip_version_check,
319
317
  args.include_archived_projects,
318
+ args.include_projects_scheduled_for_deletion,
320
319
  args.just_show_version,
321
320
  args.terminate_after_error,
322
321
  args.only_sections,
@@ -326,37 +325,26 @@ class GitLabForm:
326
325
 
327
326
  def _configure_output(self, tests=False) -> None:
328
327
  """
329
- Configures the application output using cli_ui and logging, based on debug and verbose flags:
328
+ Configures the application output using logging, based on debug and verbose flags:
330
329
 
331
- * normal mode - print cli_ui.* except debug as verbose
332
- * verbose mode - print all cli_ui.*, including debug as verbose
333
- * debug / tests mode - like above + (logging.)debug
330
+ * normal mode - logging only WARNING logs
331
+ * verbose mode - logging INFO level and above
332
+ * debug / tests mode - logging DEBUG level and above, along with rich exception tracebacks (may expose secrets)
334
333
 
335
334
  :param tests: True if we are running in tests mode
336
335
  """
337
-
338
- logging.basicConfig()
339
-
340
- if self.debug or tests:
341
- # debug / tests
342
- cli_ui_verbose = True
336
+ rich_tracebacks = False
337
+ if tests or self.debug:
343
338
  level = logging.DEBUG
339
+ rich_tracebacks = True
344
340
  elif self.verbose:
345
- # verbose
346
- cli_ui_verbose = True
347
- # de facto disabled as we don't use logging different from debug in this project
348
- level = logging.FATAL
341
+ level = logging.INFO
349
342
  else:
350
- # normal
351
- cli_ui_verbose = False
352
- # de facto disabled as we don't use logging different from debug in this project
353
- level = logging.FATAL
354
-
355
- cli_ui.setup(verbose=cli_ui_verbose)
356
- logging.getLogger().setLevel(level)
343
+ level = logging.WARNING
357
344
 
358
- fmt = logging.Formatter("%(message)s")
359
- logging.getLogger().handlers[0].setFormatter(fmt)
345
+ logging.basicConfig(
346
+ level=level, format="%(message)s", datefmt="[%X]", handlers=[RichHandler(rich_tracebacks=rich_tracebacks)]
347
+ )
360
348
 
361
349
  def _initialize_configuration_and_gitlab(self) -> Tuple[GitLab, Configuration]:
362
350
  """
@@ -378,20 +366,14 @@ class GitLabForm:
378
366
  configuration_transformers.transform(configuration)
379
367
 
380
368
  except ConfigFileNotFoundException as e:
381
- fatal(
382
- f"Config file not found at: {e}",
383
- exit_code=EXIT_INVALID_INPUT,
384
- )
369
+ critical(f"Config file not found at: {e}")
370
+ sys.exit(EXIT_INVALID_INPUT)
385
371
  except ConfigInvalidException as e:
386
- fatal(
387
- f"Invalid config:\n{e.underlying}",
388
- exit_code=EXIT_INVALID_INPUT,
389
- )
372
+ critical(f"Invalid config:\n{e.underlying}")
373
+ sys.exit(EXIT_INVALID_INPUT)
390
374
  except TestRequestFailedException as e:
391
- fatal(
392
- f"GitLab test request failed:\n{e.underlying}",
393
- exit_code=EXIT_PROCESSING_ERROR,
394
- )
375
+ critical(f"GitLab test request failed:\n{e.underlying}")
376
+ sys.exit(EXIT_INVALID_INPUT)
395
377
 
396
378
  return gitlab, configuration
397
379
 
@@ -430,9 +412,8 @@ class GitLabForm:
430
412
  "@",
431
413
  group_number,
432
414
  len(groups),
433
- yellow,
415
+ "yellow",
434
416
  f"Skipping group {group} as requested to start from {self.start_from_group}...",
435
- reset,
436
417
  )
437
418
  continue
438
419
 
@@ -444,6 +425,7 @@ class GitLabForm:
444
425
  "@",
445
426
  group_number,
446
427
  len(groups),
428
+ "black",
447
429
  f"Processing group: {group}",
448
430
  )
449
431
 
@@ -489,9 +471,8 @@ class GitLabForm:
489
471
  "*",
490
472
  project_number,
491
473
  len(projects),
492
- yellow,
474
+ "yellow",
493
475
  f"Skipping project {project_and_group} as requested to start from {self.start_from}...",
494
- reset,
495
476
  )
496
477
  continue
497
478
 
@@ -503,6 +484,7 @@ class GitLabForm:
503
484
  "*",
504
485
  project_number,
505
486
  len(projects),
487
+ "black",
506
488
  f"Processing project: {project_and_group}",
507
489
  )
508
490
 
@@ -564,15 +546,15 @@ class GitLabForm:
564
546
 
565
547
  local_version = package_version("gitlabform")
566
548
 
567
- # fmt: off
568
- tower_crane = Symbol("🏗", "")
569
- to_show = [reset, tower_crane, "GitLabForm version:", blue, local_version, reset]
570
- # fmt: on
571
- message(*to_show, sep=" ", end="")
549
+ version_text = ""
550
+ # Legacy windows console cannot support unicode emoji rendering via Rich
551
+ if not console.legacy_windows:
552
+ version_text = "🏗 "
553
+ version_text += f"GitLabForm version: [bold blue]{local_version}[/]"
572
554
 
573
555
  if skip_version_check:
574
- # just print end of the line
575
- print()
556
+ # just print version in use
557
+ console.print(version_text)
576
558
  else:
577
559
  try:
578
560
  latest_version = luddite.get_version_pypi("gitlabform")
@@ -582,24 +564,26 @@ class GitLabForm:
582
564
  error(f"Checking latest version failed:\n{e}")
583
565
  return
584
566
 
567
+ latest_stable_text = "(the latest stable is {latest_version})"
568
+
585
569
  if local_version == latest_version:
586
- # fmt: off
587
- happy = Symbol("😊", ":)")
588
- to_show = ["= the latest stable ", happy]
589
- # fmt: on
570
+ version_info = "= the latest stable"
571
+ # Legacy windows console cannot support unicode emoji rendering via Rich
572
+ if not console.legacy_windows:
573
+ version_info = f"{version_info} ☺️"
590
574
  elif version.parse(local_version) < version.parse(latest_version):
591
- # fmt: off
592
- sad = Symbol("😔", ":(")
593
- to_show = ["= outdated ", sad, " , please update! (the latest stable is ", latest_version, ")"]
594
- # fmt: on
575
+ version_info = f"= outdated, please update"
576
+ if not console.legacy_windows:
577
+ version_info = f"{version_info} 😔"
578
+ version_info = f"{version_info}! {latest_stable_text}"
595
579
  else:
596
- # fmt: off
597
- excited = Symbol("🤩", "8)")
598
- to_show = ["= pre-release ", excited, " (the latest stable is ", latest_version, ")"]
599
- # fmt: on
580
+ version_info = f"= pre-release: "
581
+ if not console.legacy_windows:
582
+ version_info = f"{version_info} 🤩"
583
+ version_info = f"{version_info} {latest_stable_text}"
600
584
 
601
585
  # complete the line with a line ending
602
- message(*to_show, sep="")
586
+ console.print(f"{version_text} {version_info}")
603
587
 
604
588
  def _get_groups_and_projects(
605
589
  self,
@@ -633,10 +617,8 @@ class GitLabForm:
633
617
  error_message = "Configuration does not have any groups or projects defined!"
634
618
  else:
635
619
  error_message = f"Project or group {target} cannot be found in GitLab!"
636
- fatal(
637
- error_message,
638
- exit_code=EXIT_INVALID_INPUT,
639
- )
620
+ critical(error_message)
621
+ sys.exit(EXIT_INVALID_INPUT)
640
622
 
641
623
  self.groups_and_projects_filters.filter(groups, projects)
642
624
 
@@ -652,7 +634,7 @@ class GitLabForm:
652
634
 
653
635
  :param entities: groups or projects
654
636
  """
655
- info_1(f"# of {entities.name} to process: {len(entities.get_effective())}")
637
+ info(f"# of {entities.name} to process: {len(entities.get_effective())}")
656
638
 
657
639
  entities_omitted = ""
658
640
  entities_verbose = f"{entities.name}: {entities.get_effective()}"
@@ -669,9 +651,9 @@ class GitLabForm:
669
651
  entities_omitted += ")"
670
652
 
671
653
  if entities_omitted:
672
- info_1(entities_omitted)
654
+ info(entities_omitted)
673
655
 
674
- verbose(entities_verbose)
656
+ info(entities_verbose)
675
657
 
676
658
  @classmethod
677
659
  def _show_summary(
@@ -695,50 +677,39 @@ class GitLabForm:
695
677
  """
696
678
 
697
679
  if len(effective_groups) > 0 or len(effective_projects) > 0:
698
- info_1(f"# of groups processed successfully: {successful_groups}")
699
- info_1(f"# of projects processed successfully: {successful_projects}")
680
+ info(f"# of groups processed successfully: {successful_groups}")
681
+ info(f"# of projects processed successfully: {successful_projects}")
700
682
 
701
683
  if len(failed_groups) > 0:
702
- info_1(red, f"# of groups failed: {len(failed_groups)}", reset)
684
+ console.print(f"# of groups failed: {len(failed_groups)}", style="red")
703
685
  for group_number in failed_groups.keys():
704
- # fmt: off
705
- info_1(red, f"Failed group {group_number}: {failed_groups[group_number]}", reset)
706
- # fmt: on
686
+ console.print(f"Failed group {group_number}: {failed_groups[group_number]}", style="red")
707
687
  if len(failed_projects) > 0:
708
- # fmt: off
709
- info_1(red, f"# of projects failed: {len(failed_projects)}", reset)
710
- # fmt: on
688
+ console.print(f"# of projects failed: {len(failed_projects)}", style="red")
711
689
  for project_number in failed_projects.keys():
712
- # fmt: off
713
- info_1(red, f"Failed project {project_number}: {failed_projects[project_number]}", reset)
714
- # fmt: on
690
+ console.print(f"Failed project {project_number}: {failed_projects[project_number]}", style="red")
715
691
 
716
692
  if len(failed_groups) > 0 or len(failed_projects) > 0:
717
693
  sys.exit(EXIT_PROCESSING_ERROR)
718
694
  elif successful_groups > 0 or successful_projects > 0:
719
- # fmt: off
720
- shine = Symbol("✨", "!!!")
721
- info_1(green, "All requested groups/projects processed successfully!", reset, shine)
722
- # fmt: on
695
+ console.print("All requested groups/projects processed successfully! :sparkles:", style="green")
723
696
  else:
724
- # fmt: off
725
- info_1(yellow, "Nothing to do.", reset)
726
- # fmt: on
697
+ console.print("Nothing to do.", style="yellow")
727
698
 
728
699
  @classmethod
729
- def _info_group_count(cls, prefix, i: int, n: int, *rest: Token, **kwargs: Any) -> None:
730
- cls._info_count(purple, prefix, i, n, *rest, **kwargs)
700
+ def _info_group_count(cls, prefix, i: int, n: int, second_color: str, second_text: str) -> None:
701
+ cls._info_count("purple", prefix, i, n, second_color, second_text)
731
702
 
732
703
  @classmethod
733
- def _info_project_count(cls, prefix, i: int, n: int, *rest: Token, **kwargs: Any) -> None:
734
- cls._info_count(green, prefix, i, n, *rest, **kwargs)
704
+ def _info_project_count(cls, prefix, i: int, n: int, second_color: str, second_text: str) -> None:
705
+ cls._info_count("green", prefix, i, n, second_color, second_text)
735
706
 
736
707
  @classmethod
737
- def _info_count(cls, color, prefix, i: int, n: int, *rest: Token, **kwargs: Any) -> None:
708
+ def _info_count(cls, color: str, prefix, i: int, n: int, second_color: str, second_text: str) -> None:
738
709
  num_digits = len(str(n))
739
710
  counter_format = f"(%{num_digits}d/%d)"
740
711
  counter_str = counter_format % (i, n)
741
- info(color, prefix, reset, counter_str, reset, *rest, **kwargs)
712
+ console.print(f"[bold {color}]{prefix} {counter_str}[/] [{second_color}]{second_text}[/]")
742
713
 
743
714
 
744
715
  class Formatter(
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  from typing import Any
2
3
 
3
4
  import os
@@ -10,8 +11,8 @@ from pathlib import Path
10
11
  from ruamel.yaml.scalarstring import ScalarString
11
12
  from types import SimpleNamespace
12
13
 
13
- from cli_ui import debug as verbose
14
- from cli_ui import fatal
14
+ from logging import critical, info
15
+
15
16
  from mergedeep import merge
16
17
  from yamlpath.common import Parsers
17
18
  from yamlpath.wrappers import ConsolePrinter
@@ -28,10 +29,8 @@ class ConfigurationCore(ABC):
28
29
 
29
30
  def __init__(self, config_path=None, config_string=None):
30
31
  if config_path and config_string:
31
- fatal(
32
- "Please initialize with either config_path or config_string, not both.",
33
- exit_code=EXIT_INVALID_INPUT,
34
- )
32
+ critical("Please initialize with either config_path or config_string, not both.")
33
+ sys.exit(EXIT_INVALID_INPUT)
35
34
  try:
36
35
  if config_string:
37
36
  self.config = self._parse_yaml(config_string, config_string=True)
@@ -44,22 +43,22 @@ class ConfigurationCore(ABC):
44
43
  # below checks are only needed in the non-test mode, when the config is read from file
45
44
 
46
45
  if self.config.get("example_config"):
47
- fatal(
46
+ critical(
48
47
  "Example config detected, aborting.\n"
49
48
  "Haven't you forgotten to use `-c <config_file>` parameter?\n"
50
49
  "If you created your config based on the example config.yml,"
51
- " then please remove 'example_config' key.",
52
- exit_code=EXIT_INVALID_INPUT,
50
+ " then please remove 'example_config' key."
53
51
  )
52
+ sys.exit(EXIT_INVALID_INPUT)
54
53
 
55
54
  if self.config.get("config_version", 1) != 4:
56
- fatal(
55
+ critical(
57
56
  "This version of GitLabForm requires 'config_version: 4' entry in the config. "
58
57
  "This ensures that if the application behavior changes in a backward-incompatible way,"
59
58
  " you won't apply unwanted configuration to your GitLab instance.\n"
60
- "Please follow this guide: https://gitlabform.github.io/gitlabform/upgrade/\n",
61
- exit_code=EXIT_INVALID_INPUT,
59
+ "Please follow this guide: https://gitlabform.github.io/gitlabform/upgrade/\n"
62
60
  )
61
+ sys.exit(EXIT_INVALID_INPUT)
63
62
 
64
63
  self._find_almost_duplicates()
65
64
 
@@ -100,11 +99,11 @@ class ConfigurationCore(ABC):
100
99
 
101
100
  if config_string:
102
101
  config = textwrap.dedent(source)
103
- verbose("Reading config from the provided string.")
102
+ info("Reading config from the provided string.")
104
103
  yaml_data, doc_loaded = Parsers.get_yaml_data(yaml, log, config, literal=True)
105
104
  else:
106
105
  config_path = source
107
- verbose(f"Reading config from file: {config_path}")
106
+ info(f"Reading config from file: {config_path}")
108
107
  yaml_data, doc_loaded = Parsers.get_yaml_data(yaml, log, config_path)
109
108
 
110
109
  if doc_loaded:
@@ -159,11 +158,11 @@ class ConfigurationCore(ABC):
159
158
  for key, value in config.items():
160
159
  if "inherit" == key:
161
160
  parent_key_description = ' under key "' + parent_key + '"' if parent_key else ""
162
- fatal(
161
+ critical(
163
162
  f'The inheritance-break flag set in "{section_name}"{parent_key_description} is invalid\n'
164
- f"because it has no higher level setting to inherit from.\n",
165
- exit_code=EXIT_INVALID_INPUT,
163
+ f"because it has no higher level setting to inherit from.\n"
166
164
  )
165
+ sys.exit(EXIT_INVALID_INPUT)
167
166
  elif type(value) in [CommentedMap, dict]:
168
167
  ConfigurationCore._validate_break_inheritance_flag(value, section_name, key)
169
168
 
@@ -194,10 +193,8 @@ class ConfigurationCore(ABC):
194
193
  replace_config_section(merged_dict, parent_path, specific_config)
195
194
  break
196
195
  elif value:
197
- fatal(
198
- f"Cannot set the inheritance break flag with true\n",
199
- exit_code=EXIT_INVALID_INPUT,
200
- )
196
+ critical(f"Cannot set the inheritance break flag with true\n")
197
+ sys.exit(EXIT_INVALID_INPUT)
201
198
  elif type(value) in [CommentedMap, dict]:
202
199
  break_inheritance(value, parent_path + (key,))
203
200
 
@@ -262,12 +259,12 @@ class ConfigurationCore(ABC):
262
259
  if self.get(path, 0):
263
260
  almost_duplicates = self._find_almost_duplicates_in(path)
264
261
  if almost_duplicates:
265
- fatal(
262
+ critical(
266
263
  f"There are almost duplicates in the keys of {path} - they differ only in case.\n"
267
264
  f"They are: {', '.join(almost_duplicates)}\n"
268
- f"This is not allowed as we ignore the case for group and project names.",
269
- exit_code=EXIT_INVALID_INPUT,
265
+ f"This is not allowed as we ignore the case for group and project names."
270
266
  )
267
+ sys.exit(EXIT_INVALID_INPUT)
271
268
 
272
269
  def _find_almost_duplicates_in(self, configuration_path):
273
270
  """
@@ -322,7 +319,5 @@ class KeyNotFoundException(Exception):
322
319
  if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
323
320
  self.key = key
324
321
  else:
325
- fatal(
326
- f"Unable to find the key: {key.replace('|', '.')}\n",
327
- exit_code=EXIT_INVALID_INPUT,
328
- )
322
+ critical(f"Unable to find the key: {key.replace('|', '.')}\n")
323
+ sys.exit(EXIT_INVALID_INPUT)
@@ -1,10 +1,10 @@
1
- from logging import debug
1
+ import sys
2
+ from logging import debug, info, critical
2
3
  from abc import ABC, abstractmethod
3
4
  from ez_yaml import ez_yaml
4
5
  from ruamel.yaml import YAML
5
6
  from types import SimpleNamespace
6
7
 
7
- from cli_ui import fatal, warning, debug as verbose
8
8
  from ruamel.yaml.comments import CommentedMap
9
9
  from yamlpath import Processor
10
10
  from yamlpath.exceptions import YAMLPathException
@@ -72,7 +72,7 @@ class UserTransformer(ConfigurationTransformer):
72
72
  logging_args = SimpleNamespace(quiet=False, verbose=False, debug=False)
73
73
  log = ConsolePrinter(logging_args)
74
74
  processor = Processor(log, configuration.config)
75
- verbose("Getting user ids for users defined in protect_environments config")
75
+ info("Getting user ids for users defined in protect_environments config")
76
76
  try:
77
77
  for node_coordinate in processor.get_nodes(
78
78
  "projects_and_groups.*.protected_environments.*.deploy_access_levels.user",
@@ -86,7 +86,7 @@ class UserTransformer(ConfigurationTransformer):
86
86
  # under the given path
87
87
  pass
88
88
 
89
- verbose("Getting user ids for users defined in merge_requests_approval_rules config")
89
+ info("Getting user ids for users defined in merge_requests_approval_rules config")
90
90
  try:
91
91
  for node_coordinate in processor.get_nodes(
92
92
  "**.merge_requests_approval_rules.*.users",
@@ -218,11 +218,11 @@ class AccessLevelsTransformer(ConfigurationTransformer):
218
218
  access_level_string = str(node_coordinate.node)
219
219
  node_coordinate.parent[node_coordinate.parentref] = AccessLevel.get_value(access_level_string)
220
220
  except KeyError:
221
- fatal(
221
+ critical(
222
222
  f"Configuration string '{access_level_string}' is not one of the valid access levels:"
223
- f" {', '.join(AccessLevel.get_canonical_names())}",
224
- exit_code=EXIT_INVALID_INPUT,
223
+ f" {', '.join(AccessLevel.get_canonical_names())}"
225
224
  )
225
+ sys.exit(EXIT_INVALID_INPUT)
226
226
  except YAMLPathException:
227
227
  # this just means that we haven't found any keys in YAML
228
228
  # under the given path
@@ -248,11 +248,11 @@ class AccessLevelsTransformer(ConfigurationTransformer):
248
248
  access_level_string
249
249
  )
250
250
  except KeyError:
251
- fatal(
251
+ critical(
252
252
  f"Configuration string '{access_level_string}' is not one of the valid access levels:"
253
- f" {', '.join(AccessLevel.get_canonical_names())}",
254
- exit_code=EXIT_INVALID_INPUT,
253
+ f" {', '.join(AccessLevel.get_canonical_names())}"
255
254
  )
255
+ sys.exit(EXIT_INVALID_INPUT)
256
256
  except YAMLPathException:
257
257
  # this just means that we haven't found any keys in YAML
258
258
  # under the given path
@@ -1,5 +1,6 @@
1
1
  import enum
2
2
  import inspect
3
+ import logging
3
4
 
4
5
  from typing import List
5
6
 
@@ -1,7 +1,7 @@
1
1
  import functools
2
2
  import os
3
3
  import re
4
- from logging import debug
4
+ from logging import debug, info, warning
5
5
  from typing import Union
6
6
  from urllib import parse
7
7
 
@@ -12,7 +12,6 @@ import requests
12
12
 
13
13
  # noinspection PyPackageRequirements
14
14
  import urllib3
15
- from cli_ui import debug as verbose, warning
16
15
  from requests.adapters import HTTPAdapter
17
16
 
18
17
  # noinspection PyPackageRequirements
@@ -73,7 +72,7 @@ class GitLabCore:
73
72
 
74
73
  try:
75
74
  version_response = self._make_requests_to_api("version")
76
- verbose(
75
+ info(
77
76
  f"Connected to GitLab version: {version_response['version']} ({version_response['revision']}), Enterprise Edition: {version_response['enterprise']}"
78
77
  )
79
78
  self.version = version_response["version"]
@@ -89,7 +88,7 @@ class GitLabCore:
89
88
  self.admin = True
90
89
  else:
91
90
  self.admin = False
92
- verbose(f"Connected as: {current_user['username']}, admin: {'yes' if self.admin else 'no'}")
91
+ info(f"Connected as: {current_user['username']}, admin: {'yes' if self.admin else 'no'}")
93
92
  if not self.admin:
94
93
  warning("Connected as non-admin. You may encounter permission issues.")
95
94
 
@@ -1,4 +1,4 @@
1
- from cli_ui import debug as verbose
1
+ from logging import info
2
2
 
3
3
  from gitlabform.gitlab.projects import GitLabProjects
4
4
 
@@ -20,7 +20,7 @@ class GitLabProjectProtectedEnvironments(GitLabProjects):
20
20
 
21
21
  # TODO: remove this when this issue is resolved -> https://gitlab.com/gitlab-org/gitlab/-/issues/378657
22
22
  if retry and (len(protected_env_cfg["deploy_access_levels"]) != len(response["deploy_access_levels"])):
23
- verbose(f'Gitlab\'s returned "deploy_access_levels" differs from the sent cfg, trying again...')
23
+ info(f'Gitlab\'s returned "deploy_access_levels" differs from the sent cfg, trying again...')
24
24
 
25
25
  self.unprotect_environment(project_and_group_name, protected_env_cfg)
26
26