gitlabform 5.1.1__tar.gz → 5.2.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.2.0}/PKG-INFO +7 -7
  2. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/__init__.py +74 -114
  3. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/configuration/core.py +23 -28
  4. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/configuration/transform.py +10 -10
  5. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/core.py +3 -4
  6. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/project_protected_environments.py +2 -2
  7. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/python_gitlab.py +2 -2
  8. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/lists/filter.py +4 -5
  9. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/lists/groups.py +4 -5
  10. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/lists/projects.py +11 -10
  11. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/output.py +7 -12
  12. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/__init__.py +3 -4
  13. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/abstract_processor.py +9 -9
  14. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_members_processor.py +19 -18
  15. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_saml_links_processor.py +1 -2
  16. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_variables_processor.py +2 -2
  17. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/multiple_entities_processor.py +17 -18
  18. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/branches_processor.py +25 -29
  19. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/files_processor.py +15 -17
  20. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/integrations_processor.py +5 -5
  21. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/job_token_scope_processor.py +1 -1
  22. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/members_processor.py +22 -22
  23. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/merge_requests_approvals.py +11 -8
  24. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/project_processor.py +7 -7
  25. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/project_security_settings.py +1 -3
  26. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/project_variables_processor.py +7 -8
  27. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/remote_mirrors_processor.py +14 -16
  28. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/resource_groups_processor.py +2 -4
  29. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/tags_processor.py +6 -8
  30. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/util/difference_logger.py +2 -2
  31. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/util/labels_processor.py +4 -5
  32. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/util/variables_processor.py +8 -9
  33. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform.egg-info/PKG-INFO +7 -7
  34. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform.egg-info/requires.txt +6 -6
  35. {gitlabform-5.1.1 → gitlabform-5.2.0}/pyproject.toml +11 -11
  36. {gitlabform-5.1.1 → gitlabform-5.2.0}/LICENSE +0 -0
  37. {gitlabform-5.1.1 → gitlabform-5.2.0}/README.md +0 -0
  38. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/configuration/__init__.py +0 -0
  39. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/configuration/common.py +0 -0
  40. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/configuration/groups.py +0 -0
  41. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/configuration/projects.py +0 -0
  42. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/constants.py +0 -0
  43. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/__init__.py +0 -0
  44. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/commits.py +0 -0
  45. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/group_badges.py +0 -0
  46. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/group_ldap_links.py +0 -0
  47. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/groups.py +0 -0
  48. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/merge_requests.py +0 -0
  49. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/pipelines.py +0 -0
  50. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/project_badges.py +0 -0
  51. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/project_deploy_keys.py +0 -0
  52. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
  53. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/projects.py +0 -0
  54. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/gitlab/variables.py +0 -0
  55. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/lists/__init__.py +0 -0
  56. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/application/__init__.py +0 -0
  57. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/application/application_settings_processor.py +0 -0
  58. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/defining_keys.py +0 -0
  59. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/__init__.py +0 -0
  60. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_badges_processor.py +0 -0
  61. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_hooks_processor.py +0 -0
  62. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_labels_processor.py +0 -0
  63. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
  64. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
  65. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/group/group_settings_processor.py +0 -0
  66. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/__init__.py +0 -0
  67. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/badges_processor.py +0 -0
  68. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
  69. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/hooks_processor.py +0 -0
  70. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
  71. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/project_labels_processor.py +0 -0
  72. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
  73. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/project_settings_processor.py +0 -0
  74. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/project/schedules_processor.py +0 -0
  75. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/shared/__init__.py +0 -0
  76. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
  77. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/util/__init__.py +0 -0
  78. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/processors/util/decorators.py +0 -0
  79. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/run.py +0 -0
  80. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform/util.py +0 -0
  81. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform.egg-info/SOURCES.txt +0 -0
  82. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform.egg-info/dependency_links.txt +0 -0
  83. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform.egg-info/entry_points.txt +0 -0
  84. {gitlabform-5.1.1 → gitlabform-5.2.0}/gitlabform.egg-info/top_level.txt +0 -0
  85. {gitlabform-5.1.1 → gitlabform-5.2.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.2.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,6 +33,8 @@ 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__(
@@ -109,10 +98,8 @@ class GitLabForm:
109
98
  sys.exit(0)
110
99
 
111
100
  if not self.target:
112
- fatal(
113
- "target parameter is required.",
114
- exit_code=EXIT_INVALID_INPUT,
115
- )
101
+ critical("target parameter is required.")
102
+ sys.exit(EXIT_INVALID_INPUT)
116
103
 
117
104
  self.gitlab, self.configuration = self._initialize_configuration_and_gitlab()
118
105
 
@@ -326,37 +313,27 @@ class GitLabForm:
326
313
 
327
314
  def _configure_output(self, tests=False) -> None:
328
315
  """
329
- Configures the application output using cli_ui and logging, based on debug and verbose flags:
316
+ Configures the application output using logging, based on debug and verbose flags:
330
317
 
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
318
+ * normal mode - logging only WARNING logs
319
+ * verbose mode - logging INFO level and above
320
+ * debug / tests mode - logging DEBUG level and above, along with rich exception tracebacks (may expose secrets)
334
321
 
335
322
  :param tests: True if we are running in tests mode
336
323
  """
337
-
338
- logging.basicConfig()
339
-
340
- if self.debug or tests:
341
- # debug / tests
342
- cli_ui_verbose = True
324
+ if tests or self.debug:
343
325
  level = logging.DEBUG
326
+ rich_tracebacks = True
344
327
  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
328
+ level = logging.INFO
329
+ rich_tracebacks = False
349
330
  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
331
+ level = logging.WARNING
332
+ rich_tracebacks = False
354
333
 
355
- cli_ui.setup(verbose=cli_ui_verbose)
356
- logging.getLogger().setLevel(level)
357
-
358
- fmt = logging.Formatter("%(message)s")
359
- logging.getLogger().handlers[0].setFormatter(fmt)
334
+ logging.basicConfig(
335
+ level=level, format="%(message)s", datefmt="[%X]", handlers=[RichHandler(rich_tracebacks=rich_tracebacks)]
336
+ )
360
337
 
361
338
  def _initialize_configuration_and_gitlab(self) -> Tuple[GitLab, Configuration]:
362
339
  """
@@ -378,20 +355,14 @@ class GitLabForm:
378
355
  configuration_transformers.transform(configuration)
379
356
 
380
357
  except ConfigFileNotFoundException as e:
381
- fatal(
382
- f"Config file not found at: {e}",
383
- exit_code=EXIT_INVALID_INPUT,
384
- )
358
+ critical(f"Config file not found at: {e}")
359
+ sys.exit(EXIT_INVALID_INPUT)
385
360
  except ConfigInvalidException as e:
386
- fatal(
387
- f"Invalid config:\n{e.underlying}",
388
- exit_code=EXIT_INVALID_INPUT,
389
- )
361
+ critical(f"Invalid config:\n{e.underlying}")
362
+ sys.exit(EXIT_INVALID_INPUT)
390
363
  except TestRequestFailedException as e:
391
- fatal(
392
- f"GitLab test request failed:\n{e.underlying}",
393
- exit_code=EXIT_PROCESSING_ERROR,
394
- )
364
+ critical(f"GitLab test request failed:\n{e.underlying}")
365
+ sys.exit(EXIT_INVALID_INPUT)
395
366
 
396
367
  return gitlab, configuration
397
368
 
@@ -430,9 +401,8 @@ class GitLabForm:
430
401
  "@",
431
402
  group_number,
432
403
  len(groups),
433
- yellow,
404
+ "yellow",
434
405
  f"Skipping group {group} as requested to start from {self.start_from_group}...",
435
- reset,
436
406
  )
437
407
  continue
438
408
 
@@ -444,6 +414,7 @@ class GitLabForm:
444
414
  "@",
445
415
  group_number,
446
416
  len(groups),
417
+ "black",
447
418
  f"Processing group: {group}",
448
419
  )
449
420
 
@@ -489,9 +460,8 @@ class GitLabForm:
489
460
  "*",
490
461
  project_number,
491
462
  len(projects),
492
- yellow,
463
+ "yellow",
493
464
  f"Skipping project {project_and_group} as requested to start from {self.start_from}...",
494
- reset,
495
465
  )
496
466
  continue
497
467
 
@@ -503,6 +473,7 @@ class GitLabForm:
503
473
  "*",
504
474
  project_number,
505
475
  len(projects),
476
+ "black",
506
477
  f"Processing project: {project_and_group}",
507
478
  )
508
479
 
@@ -564,15 +535,15 @@ class GitLabForm:
564
535
 
565
536
  local_version = package_version("gitlabform")
566
537
 
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="")
538
+ version_text = ""
539
+ # Legacy windows console cannot support unicode emoji rendering via Rich
540
+ if not console.legacy_windows:
541
+ version_text = "🏗 "
542
+ version_text += f"GitLabForm version: [bold blue]{local_version}[/]"
572
543
 
573
544
  if skip_version_check:
574
- # just print end of the line
575
- print()
545
+ # just print version in use
546
+ console.print(version_text)
576
547
  else:
577
548
  try:
578
549
  latest_version = luddite.get_version_pypi("gitlabform")
@@ -582,24 +553,26 @@ class GitLabForm:
582
553
  error(f"Checking latest version failed:\n{e}")
583
554
  return
584
555
 
556
+ latest_stable_text = "(the latest stable is {latest_version})"
557
+
585
558
  if local_version == latest_version:
586
- # fmt: off
587
- happy = Symbol("😊", ":)")
588
- to_show = ["= the latest stable ", happy]
589
- # fmt: on
559
+ version_info = "= the latest stable"
560
+ # Legacy windows console cannot support unicode emoji rendering via Rich
561
+ if not console.legacy_windows:
562
+ version_info = f"{version_info} ☺️"
590
563
  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
564
+ version_info = f"= outdated, please update"
565
+ if not console.legacy_windows:
566
+ version_info = f"{version_info} 😔"
567
+ version_info = f"{version_info}! {latest_stable_text}"
595
568
  else:
596
- # fmt: off
597
- excited = Symbol("🤩", "8)")
598
- to_show = ["= pre-release ", excited, " (the latest stable is ", latest_version, ")"]
599
- # fmt: on
569
+ version_info = f"= pre-release: "
570
+ if not console.legacy_windows:
571
+ version_info = f"{version_info} 🤩"
572
+ version_info = f"{version_info} {latest_stable_text}"
600
573
 
601
574
  # complete the line with a line ending
602
- message(*to_show, sep="")
575
+ console.print(f"{version_text} {version_info}")
603
576
 
604
577
  def _get_groups_and_projects(
605
578
  self,
@@ -633,10 +606,8 @@ class GitLabForm:
633
606
  error_message = "Configuration does not have any groups or projects defined!"
634
607
  else:
635
608
  error_message = f"Project or group {target} cannot be found in GitLab!"
636
- fatal(
637
- error_message,
638
- exit_code=EXIT_INVALID_INPUT,
639
- )
609
+ critical(error_message)
610
+ sys.exit(EXIT_INVALID_INPUT)
640
611
 
641
612
  self.groups_and_projects_filters.filter(groups, projects)
642
613
 
@@ -652,7 +623,7 @@ class GitLabForm:
652
623
 
653
624
  :param entities: groups or projects
654
625
  """
655
- info_1(f"# of {entities.name} to process: {len(entities.get_effective())}")
626
+ info(f"# of {entities.name} to process: {len(entities.get_effective())}")
656
627
 
657
628
  entities_omitted = ""
658
629
  entities_verbose = f"{entities.name}: {entities.get_effective()}"
@@ -669,9 +640,9 @@ class GitLabForm:
669
640
  entities_omitted += ")"
670
641
 
671
642
  if entities_omitted:
672
- info_1(entities_omitted)
643
+ info(entities_omitted)
673
644
 
674
- verbose(entities_verbose)
645
+ info(entities_verbose)
675
646
 
676
647
  @classmethod
677
648
  def _show_summary(
@@ -695,50 +666,39 @@ class GitLabForm:
695
666
  """
696
667
 
697
668
  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}")
669
+ info(f"# of groups processed successfully: {successful_groups}")
670
+ info(f"# of projects processed successfully: {successful_projects}")
700
671
 
701
672
  if len(failed_groups) > 0:
702
- info_1(red, f"# of groups failed: {len(failed_groups)}", reset)
673
+ console.print(f"# of groups failed: {len(failed_groups)}", style="red")
703
674
  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
675
+ console.print(f"Failed group {group_number}: {failed_groups[group_number]}", style="red")
707
676
  if len(failed_projects) > 0:
708
- # fmt: off
709
- info_1(red, f"# of projects failed: {len(failed_projects)}", reset)
710
- # fmt: on
677
+ console.print(f"# of projects failed: {len(failed_projects)}", style="red")
711
678
  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
679
+ console.print(f"Failed project {project_number}: {failed_projects[project_number]}", style="red")
715
680
 
716
681
  if len(failed_groups) > 0 or len(failed_projects) > 0:
717
682
  sys.exit(EXIT_PROCESSING_ERROR)
718
683
  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
684
+ console.print("All requested groups/projects processed successfully! :sparkles:", style="green")
723
685
  else:
724
- # fmt: off
725
- info_1(yellow, "Nothing to do.", reset)
726
- # fmt: on
686
+ console.print("Nothing to do.", style="yellow")
727
687
 
728
688
  @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)
689
+ def _info_group_count(cls, prefix, i: int, n: int, second_color: str, second_text: str) -> None:
690
+ cls._info_count("purple", prefix, i, n, second_color, second_text)
731
691
 
732
692
  @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)
693
+ def _info_project_count(cls, prefix, i: int, n: int, second_color: str, second_text: str) -> None:
694
+ cls._info_count("green", prefix, i, n, second_color, second_text)
735
695
 
736
696
  @classmethod
737
- def _info_count(cls, color, prefix, i: int, n: int, *rest: Token, **kwargs: Any) -> None:
697
+ def _info_count(cls, color: str, prefix, i: int, n: int, second_color: str, second_text: str) -> None:
738
698
  num_digits = len(str(n))
739
699
  counter_format = f"(%{num_digits}d/%d)"
740
700
  counter_str = counter_format % (i, n)
741
- info(color, prefix, reset, counter_str, reset, *rest, **kwargs)
701
+ console.print(f"[bold {color}]{prefix} {counter_str}[/] [{second_color}]{second_text}[/]")
742
702
 
743
703
 
744
704
  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,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
 
@@ -6,7 +6,7 @@ from gitlab import Gitlab, GitlabGetError, GraphQL
6
6
  from gitlab.base import RESTObject
7
7
  from gitlab.v4.objects import Group, Project, User
8
8
 
9
- from cli_ui import debug as verbose
9
+ from logging import info
10
10
 
11
11
 
12
12
  # Extends the python-gitlab class to add convenience wrappers for common functionality used within gitlabform
@@ -139,7 +139,7 @@ class PythonGitlab(Gitlab):
139
139
  }
140
140
  """
141
141
  )
142
- verbose(f"Executing graphQl query to get Member Roles for Group '{group_full_path}'")
142
+ info(f"Executing graphQl query to get Member Roles for Group '{group_full_path}'")
143
143
  result = self.graphql.execute(query)
144
144
 
145
145
  # Validate Group / MemberRoles exist
@@ -1,6 +1,7 @@
1
+ import sys
1
2
  from abc import ABC, abstractmethod
2
3
 
3
- from cli_ui import fatal
4
+ from logging import critical
4
5
 
5
6
  from gitlabform.constants import EXIT_INVALID_INPUT
6
7
  from gitlabform.lists import OmissionReason, Groups, Projects
@@ -52,10 +53,8 @@ class OmitEmptyConfigs(GroupsAndProjectsFilter):
52
53
  self.project_processors = project_processors
53
54
 
54
55
  if not self.configuration.get("projects_and_groups", {}):
55
- fatal(
56
- "Configuration has to contain non-empty 'projects_and_groups' key.",
57
- exit_code=EXIT_INVALID_INPUT,
58
- )
56
+ critical("Configuration has to contain non-empty 'projects_and_groups' key.")
57
+ sys.exit(EXIT_INVALID_INPUT)
59
58
 
60
59
  def filter(self, groups: Groups, projects: Projects) -> None:
61
60
  """