gitlabform 5.5.7__tar.gz → 6.1.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 (274) hide show
  1. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. {gitlabform-5.5.7 → gitlabform-6.1.0}/PKG-INFO +4 -5
  3. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/gitlab/gitlab.rb +4 -1
  4. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/changelog.md +28 -0
  5. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/upgrade.md +19 -0
  6. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/__init__.py +21 -14
  7. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/configuration/core.py +26 -4
  8. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/configuration/projects.py +25 -4
  9. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/configuration/transform.py +12 -11
  10. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/core.py +3 -0
  11. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/lists/projects.py +43 -0
  12. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/output.py +2 -3
  13. gitlabform-6.1.0/gitlabform/processors/group/group_saml_links_processor.py +61 -0
  14. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/__init__.py +2 -2
  15. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/files_processor.py +13 -0
  16. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/project_variables_processor.py +19 -13
  17. gitlabform-6.1.0/gitlabform/util.py +47 -0
  18. {gitlabform-5.5.7 → gitlabform-6.1.0}/pyproject.toml +10 -16
  19. {gitlabform-5.5.7 → gitlabform-6.1.0}/tbump.toml +1 -1
  20. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_group_saml_links.py +32 -0
  21. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_files_templates.py +1 -1
  22. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_remote_mirrors.py +85 -70
  23. gitlabform-6.1.0/tests/acceptance/standard/test_wildcard_patterns.py +68 -0
  24. gitlabform-6.1.0/tests/unit/configuration/test_wildcard_patterns.py +202 -0
  25. gitlabform-6.1.0/tests/unit/configuration/test_yaml_version.py +63 -0
  26. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/transform/test_access_level_transformer.py +3 -11
  27. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/transform/test_group_transformer.py +1 -7
  28. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/transform/test_implicit_name_transformer.py +2 -3
  29. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/transform/test_user_and_group_transformers.py +1 -19
  30. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/transform/test_user_transformer.py +6 -17
  31. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/gitlab/test_core.py +71 -0
  32. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_files_processor.py +15 -0
  33. gitlabform-6.1.0/tests/unit/processors/test_group_saml_links_processor.py +93 -0
  34. {gitlabform-5.5.7 → gitlabform-6.1.0}/uv.lock +136 -269
  35. gitlabform-5.5.7/gitlabform/processors/group/group_saml_links_processor.py +0 -65
  36. gitlabform-5.5.7/gitlabform/util.py +0 -7
  37. gitlabform-5.5.7/tests/unit/configuration/test_yaml_version.py +0 -36
  38. {gitlabform-5.5.7 → gitlabform-6.1.0}/.coveragerc +0 -0
  39. {gitlabform-5.5.7 → gitlabform-6.1.0}/.dockerignore +0 -0
  40. {gitlabform-5.5.7 → gitlabform-6.1.0}/.git-blame-ignore-revs +0 -0
  41. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/CODEOWNERS +0 -0
  42. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  43. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/ISSUE_TEMPLATE/question-or-other-issue.md +0 -0
  44. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/actions/setup-uv-local/README.md +0 -0
  45. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/actions/setup-uv-local/action.yml +0 -0
  46. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/dependabot.yml +0 -0
  47. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/_main.yml +0 -0
  48. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/_releases.yml +0 -0
  49. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/build.yml +0 -0
  50. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/pr-ci-workflow.yml +0 -0
  51. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/prs-entrypoint-forks.yml +0 -0
  52. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/prs-entrypoint-main.yml +0 -0
  53. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/static-analysis.yml +0 -0
  54. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/tests-acceptance.yml +0 -0
  55. {gitlabform-5.5.7 → gitlabform-6.1.0}/.github/workflows/tests-unit.yml +0 -0
  56. {gitlabform-5.5.7 → gitlabform-6.1.0}/.gitignore +0 -0
  57. {gitlabform-5.5.7 → gitlabform-6.1.0}/.lgtm.yml +0 -0
  58. {gitlabform-5.5.7 → gitlabform-6.1.0}/.mypy.ini +0 -0
  59. {gitlabform-5.5.7 → gitlabform-6.1.0}/.overrides/gitlabform-logo-favicon.png +0 -0
  60. {gitlabform-5.5.7 → gitlabform-6.1.0}/.overrides/gitlabform-logo-favicon.svg +0 -0
  61. {gitlabform-5.5.7 → gitlabform-6.1.0}/.python-version +0 -0
  62. {gitlabform-5.5.7 → gitlabform-6.1.0}/CONTRIBUTING.md +0 -0
  63. {gitlabform-5.5.7 → gitlabform-6.1.0}/Dockerfile +0 -0
  64. {gitlabform-5.5.7 → gitlabform-6.1.0}/LICENSE +0 -0
  65. {gitlabform-5.5.7 → gitlabform-6.1.0}/README.md +0 -0
  66. {gitlabform-5.5.7 → gitlabform-6.1.0}/codecov.yml +0 -0
  67. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/__init__.py +0 -0
  68. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/common.py +0 -0
  69. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/docker.py +0 -0
  70. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/docs.py +0 -0
  71. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/env.py +0 -0
  72. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/gitlab/await-healthy.sh +0 -0
  73. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/gitlab/healthcheck-and-setup.sh +0 -0
  74. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/gitlab/run_gitlab_in_docker.sh +0 -0
  75. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/gitlab/tests.Dockerfile +0 -0
  76. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/infra.py +0 -0
  77. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/main.py +0 -0
  78. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/package.py +0 -0
  79. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/qa.py +0 -0
  80. {gitlabform-5.5.7 → gitlabform-6.1.0}/dev/release.py +0 -0
  81. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/automation.md +0 -0
  82. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/contrib/coding_guidelines.md +0 -0
  83. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/contrib/implementation_design.md +0 -0
  84. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/contrib/index.md +0 -0
  85. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/contrib/local_development.md +0 -0
  86. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/contrib/releases.md +0 -0
  87. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/contrib/workflows.md +0 -0
  88. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/features.md +0 -0
  89. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/Opencast-logo.png +0 -0
  90. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/egnyte-logo.svg +0 -0
  91. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/elastic-path-logo.svg +0 -0
  92. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/gitlabform-logo-square.png +0 -0
  93. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/gitlabform-logo.png +0 -0
  94. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/gitlabform-logo.svg +0 -0
  95. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/group-ldap-links-provider.png +0 -0
  96. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/liquidlight-logo.svg +0 -0
  97. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/roche-logo.png +0 -0
  98. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/images/stroeer-online-marketing-logo.svg +0 -0
  99. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/index.md +0 -0
  100. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/installation.md +0 -0
  101. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/archive_unarchive.md +0 -0
  102. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/avatar.md +0 -0
  103. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/badges.md +0 -0
  104. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/ci_cd_variables.md +0 -0
  105. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/deploy_keys.md +0 -0
  106. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/files.md +0 -0
  107. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/group_ldap_links.md +0 -0
  108. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/group_saml_links.md +0 -0
  109. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/index.md +0 -0
  110. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/integrations.md +0 -0
  111. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/job_token_scope.md +0 -0
  112. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/labels.md +0 -0
  113. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/members.md +0 -0
  114. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/merge_requests.md +0 -0
  115. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/pipeline_schedules.md +0 -0
  116. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/project_security_settings.md +0 -0
  117. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/project_transfer.md +0 -0
  118. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/protected_branches.md +0 -0
  119. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/protected_environments.md +0 -0
  120. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/push_mirrors.md +0 -0
  121. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/push_rules.md +0 -0
  122. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/resource_groups.md +0 -0
  123. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/settings.md +0 -0
  124. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/tags_protection.md +0 -0
  125. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/reference/webhooks.md +0 -0
  126. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/requirements.md +0 -0
  127. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/running.md +0 -0
  128. {gitlabform-5.5.7 → gitlabform-6.1.0}/docs/similar_apps.md +0 -0
  129. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlab-config/config.yml +0 -0
  130. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlab-config/gitlab-config.md +0 -0
  131. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/configuration/__init__.py +0 -0
  132. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/configuration/common.py +0 -0
  133. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/configuration/groups.py +0 -0
  134. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/constants.py +0 -0
  135. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/__init__.py +0 -0
  136. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/commits.py +0 -0
  137. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/group_badges.py +0 -0
  138. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/group_ldap_links.py +0 -0
  139. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/groups.py +0 -0
  140. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/merge_requests.py +0 -0
  141. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/pipelines.py +0 -0
  142. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/project_badges.py +0 -0
  143. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/project_deploy_keys.py +0 -0
  144. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
  145. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/project_protected_environments.py +0 -0
  146. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/projects.py +0 -0
  147. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/python_gitlab.py +0 -0
  148. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/gitlab/variables.py +0 -0
  149. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/lists/__init__.py +0 -0
  150. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/lists/filter.py +0 -0
  151. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/lists/groups.py +0 -0
  152. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/__init__.py +0 -0
  153. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/abstract_processor.py +0 -0
  154. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/application/__init__.py +0 -0
  155. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/application/application_settings_processor.py +0 -0
  156. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/defining_keys.py +0 -0
  157. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/__init__.py +0 -0
  158. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_badges_processor.py +0 -0
  159. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_hooks_processor.py +0 -0
  160. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_labels_processor.py +0 -0
  161. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
  162. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_members_processor.py +0 -0
  163. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_protected_branches_processor.py +0 -0
  164. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
  165. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_settings_processor.py +0 -0
  166. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/group/group_variables_processor.py +0 -0
  167. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/multiple_entities_processor.py +0 -0
  168. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/badges_processor.py +0 -0
  169. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/branches_processor.py +0 -0
  170. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
  171. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/hooks_processor.py +0 -0
  172. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/integrations_processor.py +0 -0
  173. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
  174. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/members_processor.py +0 -0
  175. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
  176. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
  177. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/project_labels_processor.py +0 -0
  178. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/project_processor.py +0 -0
  179. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
  180. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/project_security_settings.py +0 -0
  181. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/project_settings_processor.py +0 -0
  182. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/remote_mirrors_processor.py +0 -0
  183. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/resource_groups_processor.py +0 -0
  184. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/schedules_processor.py +0 -0
  185. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/project/tags_processor.py +0 -0
  186. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/shared/__init__.py +0 -0
  187. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
  188. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/util/__init__.py +0 -0
  189. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/util/branch_protection.py +0 -0
  190. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/util/decorators.py +0 -0
  191. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/util/difference_logger.py +0 -0
  192. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/util/labels_processor.py +0 -0
  193. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/processors/util/variables_processor.py +0 -0
  194. {gitlabform-5.5.7 → gitlabform-6.1.0}/gitlabform/run.py +0 -0
  195. {gitlabform-5.5.7 → gitlabform-6.1.0}/mkdocs.yml +0 -0
  196. {gitlabform-5.5.7 → gitlabform-6.1.0}/prek.toml +0 -0
  197. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/__init__.py +0 -0
  198. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/__init__.py +0 -0
  199. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/conftest.py +0 -0
  200. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_branches.py +0 -0
  201. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_branches_users_case_insensitive.py +0 -0
  202. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_group_ldap_links.py +0 -0
  203. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_group_protected_branches.py +0 -0
  204. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_group_push_rules.py +0 -0
  205. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_group_settings.py +0 -0
  206. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_group_variables.py +0 -0
  207. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_merge_request_approval_rules.py +0 -0
  208. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_merge_request_approvals_settings.py +0 -0
  209. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_protected_environments.py +0 -0
  210. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_push_rules.py +0 -0
  211. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/premium/test_tags.py +0 -0
  212. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/__init__.py +0 -0
  213. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_application_settings.py +0 -0
  214. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_archive_project.py +0 -0
  215. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_badges.py +0 -0
  216. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_branches.py +0 -0
  217. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_deploy_keys.py +0 -0
  218. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_deploy_keys_all_projects.py +0 -0
  219. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_files.py +0 -0
  220. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_avatar.py +0 -0
  221. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_badges.py +0 -0
  222. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_hooks.py +0 -0
  223. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_labels.py +0 -0
  224. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_members_case_insensitive.py +0 -0
  225. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_members_groups.py +0 -0
  226. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_members_users.py +0 -0
  227. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_settings.py +0 -0
  228. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_group_variables.py +0 -0
  229. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_hooks.py +0 -0
  230. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_integrations.py +0 -0
  231. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_job_token_scope.py +0 -0
  232. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_members.py +0 -0
  233. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_members_add_group.py +0 -0
  234. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_members_enforce.py +0 -0
  235. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_project_avatar.py +0 -0
  236. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_project_group_members_case_insensitive.py +0 -0
  237. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_project_labels.py +0 -0
  238. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_project_members_case_insensitve.py +0 -0
  239. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_project_settings.py +0 -0
  240. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_project_variables.py +0 -0
  241. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_resource_groups.py +0 -0
  242. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_running.py +0 -0
  243. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_scheduled_for_deletion_project.py +0 -0
  244. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_schedules.py +0 -0
  245. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_tags.py +0 -0
  246. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_token_from_config.py +0 -0
  247. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/standard/test_transfer_project.py +0 -0
  248. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/ultimate/test_group_members.py +0 -0
  249. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/ultimate/test_group_settings.py +0 -0
  250. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/ultimate/test_members.py +0 -0
  251. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/ultimate/test_project_security_settings.py +0 -0
  252. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/acceptance/ultimate/test_project_settings.py +0 -0
  253. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/__init__.py +0 -0
  254. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/__init__.py +0 -0
  255. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_case_sensitivity.py +0 -0
  256. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_inheritance_break_projects_and_groups.py +0 -0
  257. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_inheritance_break_subgroups.py +0 -0
  258. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_inheritance_break_validation.py +0 -0
  259. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_projects_and_groups.py +0 -0
  260. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_skip_groups_skip_projects.py +0 -0
  261. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/configuration/test_subgroups.py +0 -0
  262. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/gitlab/test_python_gitlab.py +0 -0
  263. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/__init__.py +0 -0
  264. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_abstract_processor.py +0 -0
  265. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_abstract_processors.py +0 -0
  266. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_branches_processor.py +0 -0
  267. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_difference_logger.py +0 -0
  268. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_group_protected_branches_processor.py +0 -0
  269. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/processors/test_schedules_processor_extended_cron_pattern.py +0 -0
  270. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/test_access_levels.py +0 -0
  271. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/test_non_empty_configs_provider.py +0 -0
  272. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/test_parse_args.py +0 -0
  273. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/test_projects_provider.py +0 -0
  274. {gitlabform-5.5.7 → gitlabform-6.1.0}/tests/unit/test_utils.py +0 -0
@@ -18,3 +18,14 @@ Output of `gitlabform -V`
18
18
  **GitLab version**
19
19
 
20
20
  F.e. `13.12.4-ee`
21
+
22
+ **GitLabForm configuration**
23
+
24
+ The relevant part of your gitlabform config (optionally anonymized).
25
+ If applicable, share both the before and after config.
26
+
27
+ **Would you be open to contributing a fix?**
28
+
29
+ - [ ] Yes
30
+ - [ ] No
31
+ - [ ] Maybe
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlabform
3
- Version: 5.5.7
3
+ Version: 6.1.0
4
4
  Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
5
5
  Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
6
6
  Project-URL: Repository, https://github.com/gitlabform/gitlabform.git
@@ -19,9 +19,8 @@ Classifier: Operating System :: POSIX :: Linux
19
19
  Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Topic :: Software Development :: Version Control :: Git
21
21
  Requires-Python: >=3.12.0
22
- Requires-Dist: certifi==2026.5.20
22
+ Requires-Dist: certifi==2026.6.17
23
23
  Requires-Dist: cli-ui==0.19.0
24
- Requires-Dist: ez-yaml==1.2.0
25
24
  Requires-Dist: jinja2==3.1.6
26
25
  Requires-Dist: luddite==1.0.4
27
26
  Requires-Dist: markupsafe==3.0.3
@@ -30,8 +29,8 @@ Requires-Dist: packaging==26.2
30
29
  Requires-Dist: python-gitlab[graphql]==8.4.0
31
30
  Requires-Dist: requests==2.34.2
32
31
  Requires-Dist: rich==15.0.0
33
- Requires-Dist: ruamel-yaml==0.17.21
34
- Requires-Dist: yamlpath==3.8.2
32
+ Requires-Dist: ruamel-yaml==0.19.1
33
+ Requires-Dist: yamlpath==3.9.1
35
34
  Description-Content-Type: text/markdown
36
35
 
37
36
  [![version](https://badge.fury.io/gh/gitlabform%2Fgitlabform.svg)](https://badge.fury.io/gh/gitlabform%2Fgitlabform)
@@ -1,4 +1,7 @@
1
1
  gitlab_rails['initial_root_password']='mK9JnG7jwYdFcBNoQ3W3'
2
+ # Bind nginx on IPv4 and IPv6 loopback. Without IPv6, Sidekiq's mirror sync
3
+ # fails intermittently with ECONNREFUSED when libcurl resolves localhost to ::1.
4
+ nginx['listen_addresses'] = ['0.0.0.0', '[::]']
2
5
  registry['enable']=false
3
6
  prometheus_monitoring['enable']=false
4
7
  gitlab_rails['initial_license_file']='/etc/gitlab/Gitlab.gitlab-license'
@@ -38,4 +41,4 @@ gitlab_rails['omniauth_providers'] = [
38
41
  },
39
42
  label: 'SAML Login'
40
43
  }
41
- ]
44
+ ]
@@ -6,6 +6,34 @@ For details on how to migrate between major versions, please refer to the [upgra
6
6
 
7
7
  ---
8
8
 
9
+ ## 6.1.0
10
+
11
+ ### Features
12
+
13
+ * Add wildcard pattern support for project configuration [#1311](https://github.com/gitlabform/gitlabform/pull/1311). ([nikaro](https://github.com/nikaro))
14
+
15
+ ### Bug Fixes
16
+
17
+ * group_saml_links: stop delete-and-recreate churn on unchanged links [#1331](https://github.com/gitlabform/gitlabform/pull/1331). ([rickbrouwer](https://github.com/rickbrouwer))
18
+ * validate key in files config to avoid cryptic KeyError [#1317](https://github.com/gitlabform/gitlabform/pull/1317), resolves [#1312](https://github.com/gitlabform/gitlabform/issues/1312). ([rickbrouwer](https://github.com/rickbrouwer))
19
+ * Normalize GitLab URL while parsing configuration [#1338](https://github.com/gitlabform/gitlabform/pull/1338). ([schurzi](https://github.com/schurzi))
20
+
21
+ ### Dependencies
22
+
23
+ * Update various dependencies to newer versions.
24
+
25
+ Thanks to all the contributors of this release!
26
+
27
+ ## 6.0.0
28
+
29
+ ### Breaking Changes
30
+
31
+ **Drops default support for YAML 1.1 by upgrading dependency major versions:** [#1309](https://github.com/gitlabform/gitlabform/pull/1309). ([timknight01](https://github.com/TimKnight01))
32
+ * Drops DeepDiff and ez_yaml dependencies
33
+ * Upgrades ruamel.yaml and yamlpath to their latest versions
34
+
35
+ See [upgrade guide](upgrade.md) for important upgrade instructions and migration details.
36
+
9
37
  ## 5.5.7
10
38
 
11
39
  ### Build
@@ -2,6 +2,25 @@
2
2
 
3
3
  Some of these changes between major application versions may affect the effective configuration that would be applied if you run the application. Therefore to do the upgrade safely please follow this procedure.
4
4
 
5
+ ## From 5.\* to 6.\*
6
+
7
+ We have upgraded to new major versions of underlying YAML libraries `ezyaml`, `yamlpath` and `ruamel.yaml`.
8
+
9
+ This results in GitLabForm no longer defaulting to `yaml 1.1` support when parsing YAML configuration files.
10
+
11
+ In order to retain `yaml 1.1` configuration you **must** add the `%YAML` directive to the top of your configuration file, for example:
12
+
13
+ ```yaml
14
+ %YAML 1.1
15
+ ---
16
+ projects_and_groups:
17
+ some_group/*:
18
+ project_settings:
19
+ foo: yes
20
+ ```
21
+
22
+ Alternatively update your YAML to be compatible with `yaml 1.2` specification, for example by replacing `yes` with `true` in the above example, and any instances of `no` with `false`.
23
+
5
24
  ## From 4.\* to 5.\*
6
25
 
7
26
  ### Update `config_version`
@@ -68,8 +68,9 @@ class GitLabForm:
68
68
  self.only_sections = "all"
69
69
  self.exclude_sections = []
70
70
  self.recurse_subgroups = recurse_subgroups
71
+ self.log_level = logging.DEBUG
71
72
 
72
- self._configure_output(tests=True)
73
+ self._configure_logging()
73
74
  else:
74
75
  # normal mode
75
76
 
@@ -94,7 +95,16 @@ class GitLabForm:
94
95
  self.recurse_subgroups,
95
96
  ) = self._parse_args()
96
97
 
97
- self._configure_output()
98
+ if self.debug:
99
+ level = logging.DEBUG
100
+ elif self.verbose:
101
+ level = logging.INFO
102
+ else:
103
+ level = logging.WARNING
104
+
105
+ self.log_level = level
106
+
107
+ self._configure_logging()
98
108
 
99
109
  self._show_version(self.skip_version_check)
100
110
  if self.just_show_version:
@@ -108,7 +118,7 @@ class GitLabForm:
108
118
 
109
119
  self.application_processors = ApplicationProcessors(self.gitlab, self.configuration, self.strict)
110
120
  self.group_processors = GroupProcessors(self.gitlab, self.configuration, self.strict)
111
- self.project_processors = ProjectProcessors(self.gitlab, self.configuration, self.strict)
121
+ self.project_processors = ProjectProcessors(self.gitlab, self.configuration, self.strict, self.log_level)
112
122
  self.groups_provider = GroupsProvider(
113
123
  self.gitlab,
114
124
  self.configuration,
@@ -323,27 +333,24 @@ class GitLabForm:
323
333
  args.recurse_subsgroups,
324
334
  )
325
335
 
326
- def _configure_output(self, tests=False) -> None:
336
+ def _configure_logging(self) -> None:
327
337
  """
328
338
  Configures the application output using logging, based on debug and verbose flags:
329
339
 
330
340
  * normal mode - logging only WARNING logs
331
341
  * verbose mode - logging INFO level and above
332
342
  * debug / tests mode - logging DEBUG level and above, along with rich exception tracebacks (may expose secrets)
333
-
334
- :param tests: True if we are running in tests mode
335
343
  """
336
344
  rich_tracebacks = False
337
- if tests or self.debug:
338
- level = logging.DEBUG
345
+
346
+ if self.log_level == logging.DEBUG:
339
347
  rich_tracebacks = True
340
- elif self.verbose:
341
- level = logging.INFO
342
- else:
343
- level = logging.WARNING
344
348
 
345
349
  logging.basicConfig(
346
- level=level, format="%(message)s", datefmt="[%X]", handlers=[RichHandler(rich_tracebacks=rich_tracebacks)]
350
+ level=self.log_level,
351
+ format="%(message)s",
352
+ datefmt="[%X]",
353
+ handlers=[RichHandler(rich_tracebacks=rich_tracebacks)],
347
354
  )
348
355
 
349
356
  def _initialize_configuration_and_gitlab(self) -> Tuple[GitLab, Configuration]:
@@ -362,7 +369,7 @@ class GitLabForm:
362
369
  gitlab = GitLab(config_path=self.config)
363
370
  configuration = gitlab.get_configuration()
364
371
 
365
- configuration_transformers = ConfigurationTransformers(gitlab)
372
+ configuration_transformers = ConfigurationTransformers(gitlab, self.log_level)
366
373
  configuration_transformers.transform(configuration)
367
374
 
368
375
  except ConfigFileNotFoundException as e:
@@ -1,3 +1,4 @@
1
+ import fnmatch
1
2
  import sys
2
3
  from typing import Any
3
4
 
@@ -93,10 +94,6 @@ class ConfigurationCore(ABC):
93
94
 
94
95
  yaml = Parsers.get_yaml_editor()
95
96
 
96
- # for better backward compatibility with PyYAML (that supports only YAML 1.1) used in the previous
97
- # GitLabForm versions, let's force ruamel.yaml to use YAML version 1.1 by default too
98
- yaml.version = (1, 1)
99
-
100
97
  if config_string:
101
98
  config = textwrap.dedent(source)
102
99
  info("Reading config from the provided string.")
@@ -215,6 +212,26 @@ class ConfigurationCore(ABC):
215
212
 
216
213
  return dict(merged_dict)
217
214
 
215
+ @staticmethod
216
+ def _get_key_type(a_key: str) -> str:
217
+ """
218
+ Classify a config key used under 'projects_and_groups'.
219
+ """
220
+ if a_key == "*":
221
+ return "common"
222
+ if a_key.endswith("/*"):
223
+ return "group"
224
+ if "*" in a_key:
225
+ return "project_pattern"
226
+ return "project"
227
+
228
+ @staticmethod
229
+ def _match_pattern(pattern: str, item: str) -> bool:
230
+ """
231
+ Case-insensitive fnmatch between pattern and item.
232
+ """
233
+ return fnmatch.fnmatchcase(item.lower(), pattern.lower())
234
+
218
235
  @staticmethod
219
236
  def _get_case_insensitively(a_dict: dict, a_key: str):
220
237
  for dict_key in a_dict.keys():
@@ -235,6 +252,7 @@ class ConfigurationCore(ABC):
235
252
  if list_element == item:
236
253
  return True
237
254
 
255
+ # Support for traditional group/* skip pattern
238
256
  if (
239
257
  list_element.endswith("/*")
240
258
  and item.startswith(list_element[:-2])
@@ -242,6 +260,10 @@ class ConfigurationCore(ABC):
242
260
  ):
243
261
  return True
244
262
 
263
+ # Support for partial wildcard patterns like group/foo-*
264
+ if "*" in list_element and fnmatch.fnmatchcase(item, list_element):
265
+ return True
266
+
245
267
  return False
246
268
 
247
269
  def _find_almost_duplicates(self):
@@ -15,14 +15,14 @@ class ConfigurationProjects(ConfigurationGroups, ABC):
15
15
  depends on the groups (and common) configuration.
16
16
  """
17
17
 
18
- def get_projects(self) -> list:
18
+ def get_projects(self, key_type: str = "project") -> list:
19
19
  """
20
20
  :return: sorted list of projects names, that are EXPLICITLY defined in the config
21
21
  """
22
22
  projects = []
23
23
  projects_and_groups = self.get("projects_and_groups")
24
24
  for element in projects_and_groups.keys():
25
- if element != "*" and not element.endswith("/*"):
25
+ if self._get_key_type(element) == key_type:
26
26
  projects.append(element)
27
27
  return sorted(projects)
28
28
 
@@ -68,6 +68,27 @@ class ConfigurationProjects(ConfigurationGroups, ABC):
68
68
  """
69
69
  :param group_and_project: 'group/project'
70
70
  :return: configuration for this project or empty dict if not defined,
71
- ignoring the case
71
+ ignoring the case. Also checks for matching project patterns.
72
72
  """
73
- return self._get_case_insensitively(self.get("projects_and_groups"), group_and_project)
73
+ projects_and_groups = self.get("projects_and_groups")
74
+
75
+ # 1. Exact match
76
+ exact = self._get_case_insensitively(projects_and_groups, group_and_project)
77
+ if exact:
78
+ return exact
79
+
80
+ # 2. Best matching pattern
81
+ matches = []
82
+ for key in projects_and_groups.keys():
83
+ if self._get_key_type(key) == "project_pattern":
84
+ if self._match_pattern(key, group_and_project):
85
+ # store a 3-tuple for later sorting
86
+ prefix = key.split("*", 1)[0]
87
+ matches.append((len(prefix), -key.count("*"), key))
88
+ if matches:
89
+ # longest literal prefix wins; tie -> fewer wildcards; still tie -> first appearance
90
+ matches.sort(reverse=True)
91
+ best_key = matches[0][2]
92
+ return self._get_case_insensitively(projects_and_groups, best_key)
93
+
94
+ return {}
@@ -1,15 +1,13 @@
1
1
  import sys
2
- from logging import debug, info, critical
2
+ from logging import debug, info, critical, DEBUG
3
3
  from abc import ABC, abstractmethod
4
- from ez_yaml import ez_yaml
5
- from ruamel.yaml import YAML
6
4
  from types import SimpleNamespace
7
-
8
5
  from ruamel.yaml.comments import CommentedMap
9
6
  from yamlpath import Processor
10
7
  from yamlpath.exceptions import YAMLPathException
11
8
  from yamlpath.wrappers import ConsolePrinter
12
9
 
10
+ from gitlabform import util
13
11
  from gitlabform.constants import EXIT_INVALID_INPUT, APPROVAL_RULE_NAME
14
12
  from gitlabform.configuration import Configuration
15
13
  from gitlabform.gitlab import AccessLevel
@@ -24,23 +22,26 @@ from gitlabform.gitlab import GitLab
24
22
 
25
23
 
26
24
  class ConfigurationTransformers:
27
- def __init__(self, gitlab: GitLab):
25
+ def __init__(self, gitlab: GitLab, log_level: int):
28
26
  self.user_transformer = UserTransformer(gitlab)
29
27
  self.group_transformer = GroupTransformer(gitlab)
30
28
  self.implicit_name_transformer = ImplicitNameTransformer(gitlab)
31
29
  self.access_level_transformer = AccessLevelsTransformer(gitlab)
30
+ self.log_level = log_level
32
31
 
33
32
  def transform(self, configuration: Configuration) -> None:
34
- config_before = ez_yaml.to_string(obj=configuration.config, options={})
35
- debug(f"Config BEFORE transformations:\n{config_before}")
33
+ if self.log_level == DEBUG:
34
+ config_before = util.yaml_config_to_string(configuration.config)
35
+ debug(f"Config BEFORE transformations:\n{config_before}")
36
36
 
37
37
  self.user_transformer.transform(configuration)
38
38
  self.group_transformer.transform(configuration)
39
39
  self.implicit_name_transformer.transform(configuration)
40
40
  self.access_level_transformer.transform(configuration, last=True)
41
41
 
42
- config_after = ez_yaml.to_string(obj=configuration.config, options={})
43
- debug(f"Config AFTER transformations:\n{config_after}")
42
+ if self.log_level == DEBUG:
43
+ config_after = util.yaml_config_to_string(configuration.config)
44
+ debug(f"Config AFTER transformations:\n{config_after}")
44
45
 
45
46
 
46
47
  class ConfigurationTransformer(ABC):
@@ -58,9 +59,9 @@ class ConfigurationTransformer(ABC):
58
59
  # we needed complex ruamel.yaml's types like ordereddict and CommentedSeq
59
60
  # for transformations, but at the end convert them to simple dict and lists
60
61
  # for easier to understand debug output and tests
62
+ config_yaml_string = util.yaml_config_to_string(configuration.config)
61
63
 
62
- config_yaml_string = ez_yaml.to_string(obj=configuration.config, options={})
63
- simple_yaml_loader = YAML(typ="safe", pure=True)
64
+ simple_yaml_loader = util.configure_ruamel_yaml_loader(typ="safe", pure=True)
64
65
  configuration.config = simple_yaml_loader.load(config_yaml_string)
65
66
 
66
67
 
@@ -37,6 +37,9 @@ class GitLabCore:
37
37
  gitlab_config_from_file = self.configuration.get("gitlab", {})
38
38
  self.gitlab_config = {**default_gitlab_config, **gitlab_config_from_file}
39
39
 
40
+ if self.gitlab_config["url"]:
41
+ self.gitlab_config["url"] = self.gitlab_config["url"].rstrip("/")
42
+
40
43
  self.session = requests.Session()
41
44
 
42
45
  retries_status_forcelist = []
@@ -96,6 +96,13 @@ class ProjectsProvider(GroupsProvider):
96
96
  # defined in the configuration, but we don't need to re-check for
97
97
  # being archived or scheduled for deletion projects that we already got from groups
98
98
 
99
+ for resolved_project in self._resolve_project_patterns(groups.get_effective()):
100
+ if (
101
+ resolved_project not in projects.requested
102
+ and resolved_project not in projects_from_configuration_not_from_groups
103
+ ):
104
+ projects_from_configuration_not_from_groups.append(resolved_project)
105
+
99
106
  (
100
107
  archived_projects_from_configuration_not_from_groups,
101
108
  scheduled_for_deletion_projects_from_configuration_not_from_groups,
@@ -198,6 +205,42 @@ class ProjectsProvider(GroupsProvider):
198
205
 
199
206
  return skipped
200
207
 
208
+ def _resolve_project_patterns(self, effective_groups: list) -> list:
209
+ """
210
+ Resolve project patterns from config to concrete project paths.
211
+ Only resolves patterns whose parent group is not already in the effective groups list
212
+ to avoid redundant API calls.
213
+ """
214
+ pattern_projects = []
215
+ for pattern in self.configuration.get_projects("project_pattern"):
216
+ pattern_group = pattern.rsplit("/", 1)[0]
217
+ if pattern_group in effective_groups:
218
+ # projects from this group already fetched through normal means
219
+ continue
220
+
221
+ try:
222
+ maybe_group = self.gitlab.get_group_case_insensitive(pattern_group)
223
+ group_path = maybe_group["full_path"]
224
+ except NotFoundException:
225
+ debug(
226
+ "Group '%s' for pattern '%s' not found in GitLab, skipping",
227
+ pattern_group,
228
+ pattern,
229
+ )
230
+ continue
231
+
232
+ try:
233
+ all_project_objects = self.gitlab.get_projects(group_path, include_archived=True, only_names=False)
234
+ except NotFoundException:
235
+ all_project_objects = []
236
+
237
+ for project_object in all_project_objects:
238
+ path = project_object["path_with_namespace"]
239
+ if self.configuration._match_pattern(pattern, path) and path not in pattern_projects:
240
+ pattern_projects.append(path)
241
+
242
+ return pattern_projects
243
+
201
244
  def _get_project_transfer_source_from_config(self, project: str) -> Optional[str]:
202
245
  try:
203
246
  debug(
@@ -1,8 +1,7 @@
1
1
  import sys
2
2
  from logging import debug, critical, info
3
3
 
4
- import ez_yaml
5
-
4
+ from gitlabform import util
6
5
  from gitlabform.constants import EXIT_INVALID_INPUT, EXIT_PROCESSING_ERROR
7
6
 
8
7
 
@@ -38,7 +37,7 @@ class EffectiveConfigurationFile:
38
37
  def write_to_file(self):
39
38
  if self.output_file:
40
39
  try:
41
- yaml_configuration = ez_yaml.to_string(self.config)
40
+ yaml_configuration = util.yaml_config_to_string(self.config)
42
41
  self.output_file.write(yaml_configuration)
43
42
  self.output_file.close()
44
43
  except Exception as e:
@@ -0,0 +1,61 @@
1
+ from logging import debug
2
+ from typing import List
3
+
4
+ from gitlabform.gitlab import GitLab
5
+ from gitlab.v4.objects import Group, GroupSAMLGroupLink
6
+ from gitlabform.processors.abstract_processor import AbstractProcessor
7
+
8
+
9
+ class GroupSAMLLinksProcessor(AbstractProcessor):
10
+
11
+ def __init__(self, gitlab: GitLab):
12
+ super().__init__("group_saml_links", gitlab)
13
+
14
+ def _process_configuration(self, group_path_and_name: str, configuration: dict) -> None:
15
+ """Process the SAML links configuration for a group."""
16
+
17
+ configured_section = configuration.get("group_saml_links", {}) or {}
18
+ enforce_links = configuration.get("group_saml_links|enforce", False)
19
+
20
+ configured_links = {k: v for k, v in configured_section.items() if k != "enforce"}
21
+
22
+ group: Group = self.gl.get_group_by_path_cached(group_path_and_name)
23
+ existing_links: List[GroupSAMLGroupLink] = group.saml_group_links.list(get_all=True)
24
+ existing_by_name = {link.name: link for link in existing_links}
25
+
26
+ for _, link_configuration in configured_links.items():
27
+ saml_group_name = link_configuration.get("saml_group_name")
28
+ existing_link = existing_by_name.get(saml_group_name)
29
+
30
+ if existing_link is None:
31
+ group.saml_group_links.create(link_configuration)
32
+ continue
33
+
34
+ # GitLab API's request and response attributes differs when SAML link is created vs retrieved.
35
+ # On creation or retrieving a link, attribute is called `saml_group_name` but and returned response is 'name' instead.
36
+ # To compare existing link (retrieved from GitLab) with configured link, link configuration need to replace `saml_group_name` with `name`
37
+ expected = {**link_configuration, "name": saml_group_name}
38
+ expected.pop("saml_group_name", None)
39
+
40
+ if self._needs_update(existing_link.asdict(), expected):
41
+ # GitLab API has no update endpoint for SAML links; delete and recreate instead.
42
+ debug(f"Updating SAML link: {saml_group_name} with {link_configuration}")
43
+ existing_link.delete()
44
+ group.saml_group_links.create(link_configuration)
45
+
46
+ if enforce_links:
47
+ self._delete_extra_links(group, existing_links, configured_links)
48
+
49
+ def _delete_extra_links(
50
+ self,
51
+ group: Group,
52
+ existing: List[GroupSAMLGroupLink],
53
+ configured: dict,
54
+ ) -> None:
55
+ """Delete any SAML links that are not in the configuration."""
56
+ known_names = {link["saml_group_name"] for link in configured.values()}
57
+
58
+ for link in existing:
59
+ if link.name not in known_names:
60
+ debug(f"Deleting extra SAML link: {link.name}")
61
+ group.saml_group_links.delete(link.name)
@@ -47,7 +47,7 @@ from gitlabform.processors.project.remote_mirrors_processor import RemoteMirrors
47
47
 
48
48
 
49
49
  class ProjectProcessors(AbstractProcessors):
50
- def __init__(self, gitlab: GitLab, config: Configuration, strict: bool):
50
+ def __init__(self, gitlab: GitLab, config: Configuration, strict: bool, log_level: int):
51
51
  super().__init__(gitlab, config, strict)
52
52
  self.processors: List[AbstractProcessor] = [
53
53
  # Order of processors matter. GitLabForm will process config sections
@@ -64,7 +64,7 @@ class ProjectProcessors(AbstractProcessors):
64
64
  ProjectLabelsProcessor(gitlab),
65
65
  JobTokenScopeProcessor(gitlab),
66
66
  DeployKeysProcessor(gitlab),
67
- ProjectVariablesProcessor(gitlab),
67
+ ProjectVariablesProcessor(gitlab, log_level),
68
68
  BranchesProcessor(gitlab, strict),
69
69
  TagsProcessor(gitlab, strict),
70
70
  IntegrationsProcessor(gitlab),
@@ -24,6 +24,19 @@ class FilesProcessor(AbstractProcessor):
24
24
  self.strict = strict
25
25
  self.branch_processor = BranchesProcessor(gitlab, strict)
26
26
 
27
+ def _can_proceed(self, project_or_group: str, configuration: dict):
28
+ for file in sorted(configuration["files"]):
29
+ file_config = configuration["files"][file]
30
+ if "branches" not in file_config:
31
+ critical(
32
+ f"File '{file}' is missing the required 'branches' key."
33
+ f" Specify which branches to apply the file to using one of:"
34
+ f" 'all', 'protected', or a list of branch names (e.g. ['main'])."
35
+ )
36
+ sys.exit(EXIT_INVALID_INPUT)
37
+
38
+ return True
39
+
27
40
  def _process_configuration(self, project_and_group: str, configuration: dict):
28
41
  for file in sorted(configuration["files"]):
29
42
  project: Project = self.gl.get_project_by_path_cached(project_and_group)
@@ -1,12 +1,13 @@
1
1
  from typing import Dict, Any, List, cast
2
- from logging import warning, info
2
+ from logging import warning, info, INFO
3
3
 
4
4
  import copy
5
5
  import textwrap
6
- import ez_yaml
7
6
 
8
7
  from gitlab.exceptions import GitlabGetError
9
8
  from gitlab.v4.objects import Project, ProjectVariable
9
+
10
+ from gitlabform import util
10
11
  from gitlabform.gitlab import GitLab
11
12
  from gitlabform.processors.util.difference_logger import hide
12
13
  from gitlabform.processors.abstract_processor import AbstractProcessor
@@ -14,8 +15,9 @@ from gitlabform.processors.util.variables_processor import VariablesProcessor
14
15
 
15
16
 
16
17
  class ProjectVariablesProcessor(AbstractProcessor):
17
- def __init__(self, gitlab: GitLab):
18
+ def __init__(self, gitlab: GitLab, log_level: int):
18
19
  super().__init__("variables", gitlab)
20
+ self.log_level = log_level
19
21
  self._variables_processor = VariablesProcessor(self._needs_update)
20
22
 
21
23
  def _process_configuration(self, project_and_group: str, configuration: Dict[str, Any]) -> None:
@@ -65,12 +67,14 @@ class ProjectVariablesProcessor(AbstractProcessor):
65
67
  variables_list.append(var_dict)
66
68
 
67
69
  info(f"Variables for {project_and_group} in GitLab:")
68
- info(
69
- textwrap.indent(
70
- ez_yaml.to_string(variables_list),
71
- " ",
70
+ if self.log_level == INFO:
71
+ yaml = util.yaml_config_to_string(variables_list)
72
+ info(
73
+ textwrap.indent(
74
+ yaml,
75
+ " ",
76
+ )
72
77
  )
73
- )
74
78
  except GitlabGetError:
75
79
  info(f"Variables for {project_and_group} in GitLab cannot be checked.")
76
80
 
@@ -86,9 +90,11 @@ class ProjectVariablesProcessor(AbstractProcessor):
86
90
  for key in configured_variables.keys():
87
91
  configured_variables[key]["value"] = hide(configured_variables[key]["value"])
88
92
 
89
- info(
90
- textwrap.indent(
91
- ez_yaml.to_string(configured_variables),
92
- " ",
93
+ if self.log_level == INFO:
94
+ yaml = util.yaml_config_to_string(configured_variables)
95
+ info(
96
+ textwrap.indent(
97
+ "",
98
+ " ",
99
+ )
93
100
  )
94
- )