gitlabform 6.2.2__tar.gz → 6.2.4__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 (280) hide show
  1. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/pr-ci-workflow.yml +2 -2
  2. {gitlabform-6.2.2 → gitlabform-6.2.4}/PKG-INFO +1 -1
  3. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/gitlab/healthcheck-and-setup.sh +4 -0
  4. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/changelog.md +39 -0
  5. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/__init__.py +2 -2
  6. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/core.py +1 -1
  7. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/groups.py +1 -1
  8. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/python_gitlab.py +1 -1
  9. gitlabform-6.2.4/gitlabform/processors/group/group_ldap_links_processor.py +106 -0
  10. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/project_processor.py +26 -0
  11. {gitlabform-6.2.2 → gitlabform-6.2.4}/pyproject.toml +2 -2
  12. {gitlabform-6.2.2 → gitlabform-6.2.4}/tbump.toml +1 -1
  13. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_group_ldap_links.py +23 -0
  14. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/gitlab/test_core.py +14 -0
  15. gitlabform-6.2.4/tests/unit/gitlab/test_groups.py +15 -0
  16. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/gitlab/test_python_gitlab.py +8 -0
  17. gitlabform-6.2.4/tests/unit/processors/test_group_ldap_links_processor.py +165 -0
  18. {gitlabform-6.2.2 → gitlabform-6.2.4}/uv.lock +7 -7
  19. gitlabform-6.2.2/gitlabform/gitlab/group_ldap_links.py +0 -40
  20. gitlabform-6.2.2/gitlabform/processors/group/group_ldap_links_processor.py +0 -16
  21. {gitlabform-6.2.2 → gitlabform-6.2.4}/.coveragerc +0 -0
  22. {gitlabform-6.2.2 → gitlabform-6.2.4}/.dockerignore +0 -0
  23. {gitlabform-6.2.2 → gitlabform-6.2.4}/.git-blame-ignore-revs +0 -0
  24. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/CODEOWNERS +0 -0
  25. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  26. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  27. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/ISSUE_TEMPLATE/question-or-other-issue.md +0 -0
  28. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/actions/setup-uv-local/README.md +0 -0
  29. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/actions/setup-uv-local/action.yml +0 -0
  30. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/dependabot.yml +0 -0
  31. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/_main.yml +0 -0
  32. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/_releases.yml +0 -0
  33. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/build.yml +0 -0
  34. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/codecov-upload.yml +0 -0
  35. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/prs-entrypoint-forks.yml +0 -0
  36. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/prs-entrypoint-main.yml +0 -0
  37. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/static-analysis.yml +0 -0
  38. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/tests-acceptance.yml +0 -0
  39. {gitlabform-6.2.2 → gitlabform-6.2.4}/.github/workflows/tests-unit.yml +0 -0
  40. {gitlabform-6.2.2 → gitlabform-6.2.4}/.gitignore +0 -0
  41. {gitlabform-6.2.2 → gitlabform-6.2.4}/.lgtm.yml +0 -0
  42. {gitlabform-6.2.2 → gitlabform-6.2.4}/.mypy.ini +0 -0
  43. {gitlabform-6.2.2 → gitlabform-6.2.4}/.overrides/gitlabform-logo-favicon.png +0 -0
  44. {gitlabform-6.2.2 → gitlabform-6.2.4}/.overrides/gitlabform-logo-favicon.svg +0 -0
  45. {gitlabform-6.2.2 → gitlabform-6.2.4}/.python-version +0 -0
  46. {gitlabform-6.2.2 → gitlabform-6.2.4}/CONTRIBUTING.md +0 -0
  47. {gitlabform-6.2.2 → gitlabform-6.2.4}/Dockerfile +0 -0
  48. {gitlabform-6.2.2 → gitlabform-6.2.4}/LICENSE +0 -0
  49. {gitlabform-6.2.2 → gitlabform-6.2.4}/README.md +0 -0
  50. {gitlabform-6.2.2 → gitlabform-6.2.4}/codecov.yml +0 -0
  51. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/__init__.py +0 -0
  52. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/common.py +0 -0
  53. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/docker.py +0 -0
  54. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/docs.py +0 -0
  55. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/env.py +0 -0
  56. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/gitlab/await-healthy.sh +0 -0
  57. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/gitlab/gitlab.rb +0 -0
  58. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/gitlab/run_gitlab_in_docker.sh +0 -0
  59. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/gitlab/tests.Dockerfile +0 -0
  60. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/infra.py +0 -0
  61. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/main.py +0 -0
  62. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/package.py +0 -0
  63. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/qa.py +0 -0
  64. {gitlabform-6.2.2 → gitlabform-6.2.4}/dev/release.py +0 -0
  65. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/automation.md +0 -0
  66. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/contrib/coding_guidelines.md +0 -0
  67. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/contrib/implementation_design.md +0 -0
  68. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/contrib/index.md +0 -0
  69. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/contrib/local_development.md +0 -0
  70. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/contrib/releases.md +0 -0
  71. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/contrib/workflows.md +0 -0
  72. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/features.md +0 -0
  73. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/Opencast-logo.png +0 -0
  74. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/egnyte-logo.svg +0 -0
  75. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/elastic-path-logo.svg +0 -0
  76. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/gitlabform-logo-square.png +0 -0
  77. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/gitlabform-logo.png +0 -0
  78. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/gitlabform-logo.svg +0 -0
  79. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/group-ldap-links-provider.png +0 -0
  80. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/liquidlight-logo.svg +0 -0
  81. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/roche-logo.png +0 -0
  82. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/stroeer-online-marketing-logo.svg +0 -0
  83. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/images/ti8m-logo.png +0 -0
  84. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/index.md +0 -0
  85. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/installation.md +0 -0
  86. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/archive_unarchive.md +0 -0
  87. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/avatar.md +0 -0
  88. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/badges.md +0 -0
  89. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/ci_cd_variables.md +0 -0
  90. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/deploy_keys.md +0 -0
  91. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/files.md +0 -0
  92. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/group_ldap_links.md +0 -0
  93. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/group_saml_links.md +0 -0
  94. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/index.md +0 -0
  95. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/integrations.md +0 -0
  96. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/job_token_scope.md +0 -0
  97. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/labels.md +0 -0
  98. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/members.md +0 -0
  99. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/merge_requests.md +0 -0
  100. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/pipeline_schedules.md +0 -0
  101. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/project_security_settings.md +0 -0
  102. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/project_transfer.md +0 -0
  103. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/protected_branches.md +0 -0
  104. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/protected_environments.md +0 -0
  105. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/push_mirrors.md +0 -0
  106. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/push_rules.md +0 -0
  107. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/resource_groups.md +0 -0
  108. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/settings.md +0 -0
  109. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/tags_protection.md +0 -0
  110. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/reference/webhooks.md +0 -0
  111. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/requirements.md +0 -0
  112. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/running.md +0 -0
  113. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/similar_apps.md +0 -0
  114. {gitlabform-6.2.2 → gitlabform-6.2.4}/docs/upgrade.md +0 -0
  115. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlab-config/config.yml +0 -0
  116. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlab-config/gitlab-config.md +0 -0
  117. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/__init__.py +0 -0
  118. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/configuration/__init__.py +0 -0
  119. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/configuration/common.py +0 -0
  120. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/configuration/core.py +0 -0
  121. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/configuration/groups.py +0 -0
  122. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/configuration/projects.py +0 -0
  123. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/configuration/transform.py +0 -0
  124. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/constants.py +0 -0
  125. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/commits.py +0 -0
  126. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/merge_requests.py +0 -0
  127. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/pipelines.py +0 -0
  128. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
  129. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/project_protected_environments.py +0 -0
  130. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/projects.py +0 -0
  131. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/gitlab/variables.py +0 -0
  132. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/lists/__init__.py +0 -0
  133. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/lists/filter.py +0 -0
  134. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/lists/groups.py +0 -0
  135. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/lists/projects.py +0 -0
  136. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/output.py +0 -0
  137. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/__init__.py +0 -0
  138. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/abstract_processor.py +0 -0
  139. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/application/__init__.py +0 -0
  140. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/application/application_settings_processor.py +0 -0
  141. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/defining_keys.py +0 -0
  142. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/__init__.py +0 -0
  143. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_badges_processor.py +0 -0
  144. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_hooks_processor.py +0 -0
  145. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_labels_processor.py +0 -0
  146. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_members_processor.py +0 -0
  147. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_protected_branches_processor.py +0 -0
  148. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
  149. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_saml_links_processor.py +0 -0
  150. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_settings_processor.py +0 -0
  151. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/group/group_variables_processor.py +0 -0
  152. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/multiple_entities_processor.py +0 -0
  153. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/__init__.py +0 -0
  154. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/badges_processor.py +0 -0
  155. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/branches_processor.py +0 -0
  156. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
  157. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/files_processor.py +0 -0
  158. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/hooks_processor.py +0 -0
  159. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/integrations_processor.py +0 -0
  160. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
  161. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/members_processor.py +0 -0
  162. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
  163. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
  164. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/project_labels_processor.py +0 -0
  165. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
  166. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/project_security_settings.py +0 -0
  167. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/project_settings_processor.py +0 -0
  168. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/project_variables_processor.py +0 -0
  169. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/remote_mirrors_processor.py +0 -0
  170. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/resource_groups_processor.py +0 -0
  171. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/schedules_processor.py +0 -0
  172. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/project/tags_processor.py +0 -0
  173. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/shared/__init__.py +0 -0
  174. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
  175. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/__init__.py +0 -0
  176. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/badges_processor.py +0 -0
  177. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/branch_protection.py +0 -0
  178. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/decorators.py +0 -0
  179. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/difference_logger.py +0 -0
  180. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/labels_processor.py +0 -0
  181. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/processors/util/variables_processor.py +0 -0
  182. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/run.py +0 -0
  183. {gitlabform-6.2.2 → gitlabform-6.2.4}/gitlabform/util.py +0 -0
  184. {gitlabform-6.2.2 → gitlabform-6.2.4}/mkdocs.yml +0 -0
  185. {gitlabform-6.2.2 → gitlabform-6.2.4}/prek.toml +0 -0
  186. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/__init__.py +0 -0
  187. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/__init__.py +0 -0
  188. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/conftest.py +0 -0
  189. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_branches.py +0 -0
  190. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_branches_users_case_insensitive.py +0 -0
  191. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_group_protected_branches.py +0 -0
  192. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_group_push_rules.py +0 -0
  193. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_group_saml_links.py +0 -0
  194. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_group_settings.py +0 -0
  195. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_group_variables.py +0 -0
  196. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_merge_request_approval_rules.py +0 -0
  197. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_merge_request_approvals_settings.py +0 -0
  198. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_protected_environments.py +0 -0
  199. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_push_rules.py +0 -0
  200. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/premium/test_tags.py +0 -0
  201. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/__init__.py +0 -0
  202. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_application_settings.py +0 -0
  203. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_archive_project.py +0 -0
  204. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_badges.py +0 -0
  205. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_branches.py +0 -0
  206. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_deploy_keys.py +0 -0
  207. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_deploy_keys_all_projects.py +0 -0
  208. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_files.py +0 -0
  209. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_files_templates.py +0 -0
  210. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_avatar.py +0 -0
  211. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_badges.py +0 -0
  212. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_hooks.py +0 -0
  213. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_labels.py +0 -0
  214. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_members_case_insensitive.py +0 -0
  215. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_members_groups.py +0 -0
  216. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_members_users.py +0 -0
  217. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_settings.py +0 -0
  218. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_group_variables.py +0 -0
  219. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_hooks.py +0 -0
  220. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_integrations.py +0 -0
  221. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_job_token_scope.py +0 -0
  222. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_members.py +0 -0
  223. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_members_add_group.py +0 -0
  224. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_members_enforce.py +0 -0
  225. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_project_avatar.py +0 -0
  226. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_project_group_members_case_insensitive.py +0 -0
  227. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_project_labels.py +0 -0
  228. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_project_members_case_insensitve.py +0 -0
  229. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_project_settings.py +0 -0
  230. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_project_variables.py +0 -0
  231. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_remote_mirrors.py +0 -0
  232. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_resource_groups.py +0 -0
  233. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_running.py +0 -0
  234. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_scheduled_for_deletion_project.py +0 -0
  235. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_schedules.py +0 -0
  236. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_tags.py +0 -0
  237. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_token_from_config.py +0 -0
  238. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_transfer_project.py +0 -0
  239. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/standard/test_wildcard_patterns.py +0 -0
  240. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/ultimate/test_group_members.py +0 -0
  241. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/ultimate/test_group_settings.py +0 -0
  242. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/ultimate/test_members.py +0 -0
  243. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/ultimate/test_project_security_settings.py +0 -0
  244. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/acceptance/ultimate/test_project_settings.py +0 -0
  245. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/__init__.py +0 -0
  246. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/__init__.py +0 -0
  247. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_case_sensitivity.py +0 -0
  248. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_inheritance_break_projects_and_groups.py +0 -0
  249. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_inheritance_break_subgroups.py +0 -0
  250. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_inheritance_break_validation.py +0 -0
  251. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_projects_and_groups.py +0 -0
  252. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_skip_groups_skip_projects.py +0 -0
  253. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_subgroups.py +0 -0
  254. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_wildcard_patterns.py +0 -0
  255. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/test_yaml_version.py +0 -0
  256. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/transform/test_access_level_transformer.py +0 -0
  257. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/transform/test_group_transformer.py +0 -0
  258. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/transform/test_implicit_name_transformer.py +0 -0
  259. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/transform/test_user_and_group_transformers.py +0 -0
  260. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/configuration/transform/test_user_transformer.py +0 -0
  261. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/__init__.py +0 -0
  262. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_abstract_processor.py +0 -0
  263. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_abstract_processors.py +0 -0
  264. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_branches_processor.py +0 -0
  265. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_deploy_keys_processor.py +0 -0
  266. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_difference_logger.py +0 -0
  267. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_files_processor.py +0 -0
  268. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_group_members_processor.py +0 -0
  269. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_group_protected_branches_processor.py +0 -0
  270. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_group_saml_links_processor.py +0 -0
  271. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_merge_requests_approval_rules_processor.py +0 -0
  272. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_schedules_processor_extended_cron_pattern.py +0 -0
  273. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/processors/test_util_badges_processor.py +0 -0
  274. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_access_levels.py +0 -0
  275. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_non_empty_configs_provider.py +0 -0
  276. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_output.py +0 -0
  277. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_parse_args.py +0 -0
  278. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_projects_provider.py +0 -0
  279. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_show_summary.py +0 -0
  280. {gitlabform-6.2.2 → gitlabform-6.2.4}/tests/unit/test_utils.py +0 -0
@@ -77,7 +77,7 @@ jobs:
77
77
  # uses: actions/setup-example@v1
78
78
 
79
79
  - name: Initialize CodeQL
80
- uses: github/codeql-action/init@v4.38.0
80
+ uses: github/codeql-action/init@v4.38.2
81
81
  with:
82
82
  languages: ${{ matrix.language }}
83
83
  build-mode: ${{ matrix.build-mode }}
@@ -89,6 +89,6 @@ jobs:
89
89
  # queries: security-extended,security-and-quality
90
90
 
91
91
  - name: Perform CodeQL Analysis
92
- uses: github/codeql-action/analyze@v4.38.0
92
+ uses: github/codeql-action/analyze@v4.38.2
93
93
  with:
94
94
  category: "/language:${{matrix.language}}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gitlabform
3
- Version: 6.2.2
3
+ Version: 6.2.4
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
@@ -34,6 +34,10 @@ test -f $done || {
34
34
  printf 'expires_at: 365.days.from_now);'
35
35
  printf "gitlabform_token.set_token('token-string-here123');"
36
36
  printf 'gitlabform_token.save!;'
37
+ # Job token scope enforcement is enabled by default, which blocks the tests
38
+ # that disable it per project
39
+ printf 'settings = Gitlab::CurrentSettings.current_application_settings;'
40
+ printf 'settings.update!(enforce_ci_inbound_job_token_scope_enabled: false) if settings.respond_to?(:enforce_ci_inbound_job_token_scope_enabled);'
37
41
  ) | gitlab-rails console
38
42
 
39
43
  touch $done
@@ -6,10 +6,47 @@ For details on how to migrate between major versions, please refer to the [upgra
6
6
 
7
7
  ---
8
8
 
9
+ ## 6.2.4
10
+
11
+ ### Performance
12
+
13
+ * skip fetching projects when querying groups [#1436](https://github.com/gitlabform/gitlabform/pull/1436). ([ryanpham-gitlab](https://github.com/ryanpham-gitlab))
14
+
15
+ ### Refactors
16
+
17
+ * migrate group ldap links processor to python-gitlab [#1428](https://github.com/gitlabform/gitlabform/pull/1428). ([rickbrouwer](https://github.com/rickbrouwer))
18
+
19
+ ### Dependencies
20
+
21
+ * Update various dependencies to newer versions.
22
+
23
+ Thanks to all the contributors of this release!
24
+
25
+ ## 6.2.3
26
+
27
+ ### Bug Fixes
28
+
29
+ * wait for asynchronous project transfer to complete [#1432](https://github.com/gitlabform/gitlabform/pull/1432). ([rickbrouwer](https://github.com/rickbrouwer))
30
+
31
+ ### Documentation
32
+
33
+ * add missing entries into v6.2.2 changelog [#1426](https://github.com/gitlabform/gitlabform/pull/1426). ([amimas](https://github.com/amimas))
34
+
35
+ ### Tests
36
+
37
+ * disable job token scope enforcement in the test environment [#1433](https://github.com/gitlabform/gitlabform/pull/1433). ([rickbrouwer](https://github.com/rickbrouwer))
38
+
39
+ ### Dependencies
40
+
41
+ * Update various dependencies to newer versions.
42
+
43
+ Thanks to all the contributors of this release!
44
+
9
45
  ## 6.2.2
10
46
 
11
47
  ### Bug Fixes
12
48
 
49
+ * diff log should not require verbose [#1343](https://github.com/gitlabform/gitlabform/pull/1343). ([timknight01](https://github.com/TimKnight01))
13
50
  * prevent KeyError when using --output with application settings [#1384](https://github.com/gitlabform/gitlabform/pull/1384). ([rickbrouwer](https://github.com/rickbrouwer))
14
51
 
15
52
  ### Documentation
@@ -19,6 +56,7 @@ For details on how to migrate between major versions, please refer to the [upgra
19
56
 
20
57
  ### Refactors
21
58
 
59
+ * migrate deploy-keys processor to python-gitlab [#1422](https://github.com/gitlabform/gitlabform/pull/1422). ([rickbrouwer](https://github.com/rickbrouwer))
22
60
  * migrate badges processor to python-gitlab [#1388](https://github.com/gitlabform/gitlabform/pull/1388). ([rickbrouwer](https://github.com/rickbrouwer))
23
61
  * centralize dry-run diff in AbstractProcessor [#1353](https://github.com/gitlabform/gitlabform/pull/1353). ([rickbrouwer](https://github.com/rickbrouwer))
24
62
 
@@ -29,6 +67,7 @@ For details on how to migrate between major versions, please refer to the [upgra
29
67
 
30
68
  ### Chores
31
69
 
70
+ * reduce uv dev dependency update cadence [#1423](https://github.com/gitlabform/gitlabform/pull/1423). ([amimas](https://github.com/amimas))
32
71
  * update default PR reviewers [#1375](https://github.com/gitlabform/gitlabform/pull/1375). ([rickbrouwer](https://github.com/rickbrouwer))
33
72
 
34
73
  ### Dependencies
@@ -7,7 +7,7 @@ from typing import List
7
7
  from gitlab import Gitlab as GitlabClient, GraphQL
8
8
 
9
9
  from gitlabform.gitlab.commits import GitLabCommits
10
- from gitlabform.gitlab.group_ldap_links import GitLabGroupLDAPLinks
10
+ from gitlabform.gitlab.groups import GitLabGroups
11
11
  from gitlabform.gitlab.merge_requests import GitLabMergeRequests
12
12
  from gitlabform.gitlab.pipelines import GitLabPipelines
13
13
  from gitlabform.gitlab.project_protected_environments import (
@@ -48,8 +48,8 @@ class AccessLevel(enum.IntEnum):
48
48
 
49
49
  class GitLab(
50
50
  GitLabCommits,
51
+ GitLabGroups,
51
52
  GitLabMergeRequests,
52
- GitLabGroupLDAPLinks,
53
53
  GitLabPipelines,
54
54
  GitLabProjectProtectedEnvironments,
55
55
  GitLabProjectMergeRequestsApprovals,
@@ -154,7 +154,7 @@ class GitLabCore:
154
154
 
155
155
  @functools.lru_cache()
156
156
  def _get_group_id(self, path) -> int:
157
- group = self._make_requests_to_api("groups/%s", path, "GET")
157
+ group = self._make_requests_to_api("groups/%s?with_projects=false", path, "GET")
158
158
  return int(group["id"])
159
159
 
160
160
  @functools.lru_cache()
@@ -29,7 +29,7 @@ class GitLabGroups(GitLabCore):
29
29
  raise NotFoundException(f"Group/subgroup with path '{some_string}' not found.")
30
30
 
31
31
  def get_group(self, name):
32
- return self._make_requests_to_api("groups/%s", name)
32
+ return self._make_requests_to_api("groups/%s?with_projects=false", name)
33
33
 
34
34
  def get_group_descendants(self, group_id_or_path):
35
35
  return self._make_requests_to_api("groups/%s/descendant_groups", group_id_or_path)
@@ -84,7 +84,7 @@ class PythonGitlab(Gitlab):
84
84
 
85
85
  @functools.lru_cache()
86
86
  def get_group_by_path_cached(self, groupname: str) -> Group:
87
- group: Group = self.groups.get(groupname)
87
+ group: Group = self.groups.get(groupname, with_projects=False)
88
88
  if group:
89
89
  return group
90
90
 
@@ -0,0 +1,106 @@
1
+ from logging import info
2
+ from typing import Dict, List, Optional, Tuple
3
+
4
+ from gitlab.exceptions import GitlabCreateError, GitlabListError
5
+ from gitlab.v4.objects import Group, GroupLDAPGroupLink
6
+
7
+ from gitlabform.gitlab import GitLab
8
+ from gitlabform.processors.abstract_processor import AbstractProcessor
9
+
10
+ # A link is identified by provider + exactly one of (cn, filter).
11
+ DefiningKey = Tuple[Optional[str], Optional[str], Optional[str]]
12
+
13
+
14
+ class GroupLDAPLinksProcessor(AbstractProcessor):
15
+ def __init__(self, gitlab: GitLab):
16
+ super().__init__("group_ldap_links", gitlab)
17
+
18
+ def _process_configuration(self, group_path_and_name: str, configuration: Dict):
19
+ configured_links: Dict = configuration.get("group_ldap_links", {})
20
+ enforce = configured_links.pop("enforce", False)
21
+
22
+ group: Group = self.gl.get_group_by_path_cached(group_path_and_name)
23
+ existing_links: List[GroupLDAPGroupLink] = self._list_existing_links(group)
24
+
25
+ processed_keys: set = set()
26
+
27
+ for entity_name, link_config in configured_links.items():
28
+ key = self._defining_key_of_config(link_config)
29
+ matching = next(
30
+ (link for link in existing_links if self._defining_key_of_link(link) == key),
31
+ None,
32
+ )
33
+
34
+ if link_config.get("delete", False):
35
+ if matching:
36
+ info(f"Deleting {entity_name} of {self.configuration_name} in {group_path_and_name}")
37
+ matching.delete()
38
+ else:
39
+ info(
40
+ f"Not deleting {entity_name} of {self.configuration_name} in {group_path_and_name},"
41
+ f" because it doesn't exist"
42
+ )
43
+ processed_keys.add(key)
44
+ continue
45
+
46
+ if matching:
47
+ if self._needs_update(matching.asdict(), link_config):
48
+ # The API has no endpoint to update an LDAP link
49
+ # (https://docs.gitlab.com/api/group_ldap_links/), so we recreate it.
50
+ info(f" * Recreating {entity_name} of {self.configuration_name} in {group_path_and_name}")
51
+ matching.delete()
52
+ self._create_link(group, link_config)
53
+ else:
54
+ info(
55
+ f" * {entity_name} of {self.configuration_name} in {group_path_and_name}"
56
+ f" doesn't need an update."
57
+ )
58
+ else:
59
+ info(f" * Adding {entity_name} of {self.configuration_name} in {group_path_and_name}")
60
+ self._create_link(group, link_config)
61
+ processed_keys.add(key)
62
+
63
+ if enforce:
64
+ for existing in existing_links:
65
+ if self._defining_key_of_link(existing) not in processed_keys:
66
+ info(
67
+ f"Deleting LDAP link (provider={existing.provider},"
68
+ f" cn={getattr(existing, 'cn', None)},"
69
+ f" filter={getattr(existing, 'filter', None)})"
70
+ f" of {self.configuration_name} in {group_path_and_name}"
71
+ f" as it's not in config and enforce is set to true."
72
+ )
73
+ existing.delete()
74
+
75
+ @staticmethod
76
+ def _list_existing_links(group: Group) -> List[GroupLDAPGroupLink]:
77
+ # GitLab returns 404 (not an empty list) when a group has no LDAP links configured;
78
+ # python-gitlab surfaces that as GitlabListError.
79
+ try:
80
+ return group.ldap_group_links.list(get_all=True)
81
+ except GitlabListError as e:
82
+ if e.response_code == 404:
83
+ return []
84
+ raise
85
+
86
+ @staticmethod
87
+ def _create_link(group: Group, link_config: Dict) -> None:
88
+ try:
89
+ group.ldap_group_links.create(link_config)
90
+ except GitlabCreateError as e:
91
+ # GitLab returns 404 instead of 400 when the create parameters are invalid
92
+ # (e.g. a non-existent provider), so a 404 here does not mean a missing group.
93
+ if e.response_code == 404:
94
+ raise GitlabCreateError(
95
+ f"Invalid parameters for an LDAP group link: {link_config}",
96
+ response_code=404,
97
+ ) from e
98
+ raise
99
+
100
+ @staticmethod
101
+ def _defining_key_of_config(link_config: Dict) -> DefiningKey:
102
+ return (link_config.get("provider"), link_config.get("cn"), link_config.get("filter"))
103
+
104
+ @staticmethod
105
+ def _defining_key_of_link(link: GroupLDAPGroupLink) -> DefiningKey:
106
+ return (link.provider, getattr(link, "cn", None), getattr(link, "filter", None))
@@ -1,4 +1,6 @@
1
1
  from logging import info, error
2
+ from time import sleep
3
+
2
4
  from gitlabform.gitlab import GitLab
3
5
  from gitlabform.processors.abstract_processor import AbstractProcessor
4
6
  from gitlab import GitlabGetError, GitlabTransferProjectError
@@ -40,6 +42,7 @@ class ProjectProcessor(AbstractProcessor):
40
42
  project_transfer_destination_group, _ = project_and_group.rsplit("/", 1)
41
43
  info(f"Transferring project to '{project_transfer_destination_group}' group...")
42
44
  project_to_be_transferred.transfer(project_transfer_destination_group)
45
+ self._wait_for_transfer(project_to_be_transferred.id, project_transfer_destination_group)
43
46
  # TODO: Catch GitlabTransferProjectError exception.
44
47
  # The above code can run into exception for various reasons.
45
48
  # We should catch this exception and log a custom error message with hints.
@@ -60,3 +63,26 @@ class ProjectProcessor(AbstractProcessor):
60
63
  elif configuration["project"].get("archive") is False:
61
64
  info("Unarchiving project...")
62
65
  project.unarchive()
66
+
67
+ def _wait_for_transfer(self, project_id: int, destination_group: str) -> None:
68
+ # GitLab 19.4 and newer transfer projects asynchronously, so the API may still
69
+ # report the old namespace right after the transfer request returns
70
+ max_retries = 60
71
+ wait_before_retry = 2
72
+ retry = 0
73
+
74
+ while True:
75
+ current_namespace = self.gl.projects.get(project_id).namespace["full_path"]
76
+
77
+ if current_namespace == destination_group:
78
+ return
79
+
80
+ retry += 1
81
+
82
+ if retry > max_retries:
83
+ raise GitlabTransferProjectError(
84
+ f"Project is still in '{current_namespace}' after waiting for the transfer"
85
+ f" to '{destination_group}' to complete"
86
+ )
87
+
88
+ sleep(wait_before_retry)
@@ -1,7 +1,7 @@
1
1
  [build-system]
2
2
  # Modern build backend (PEP 517). Pinned for reproducibility.
3
3
  # hatchling is preferred for uv-centric projects.
4
- requires = ["hatchling==1.32.0"]
4
+ requires = ["hatchling==1.32.4"]
5
5
  build-backend = "hatchling.build"
6
6
 
7
7
  # Build backend configuration.
@@ -10,7 +10,7 @@ packages = ["gitlabform"]
10
10
 
11
11
  [project]
12
12
  name = "gitlabform"
13
- version = "6.2.2"
13
+ version = "6.2.4"
14
14
  requires-python = ">=3.12.0"
15
15
  authors = [{ name = "Greg Dubicki and Contributors" }]
16
16
  description = "🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML"
@@ -2,7 +2,7 @@
2
2
  github_url = "https://github.com/gitlabform/gitlabform/"
3
3
 
4
4
  [version]
5
- current = "6.2.2"
5
+ current = "6.2.4"
6
6
 
7
7
  # Example of a semver regexp.
8
8
  # Make sure this matches current_version before
@@ -120,3 +120,26 @@ class TestGroupLDAPLinks:
120
120
  assert ldap_links[0].provider == "LDAP Main"
121
121
  assert ldap_links[0].cn == "devops"
122
122
  assert ldap_links[0].group_access == AccessLevel.get_value("developer")
123
+
124
+ def test__delete_ldap_links(self, gl, group):
125
+ """Test deleting an LDAP link of a group with 'delete: true'."""
126
+
127
+ # Verify group has 1 ldap link - last state of the group in previous test
128
+ assert len(group.ldap_group_links.list()) == 1
129
+
130
+ delete_ldap_link = f"""
131
+ projects_and_groups:
132
+ {group.full_path}/*:
133
+ group_ldap_links:
134
+ devops_users:
135
+ provider: LDAP Main
136
+ cn: devops
137
+ delete: true
138
+ """
139
+ run_gitlabform(delete_ldap_link, group)
140
+
141
+ # Verify that the last LDAP link was removed.
142
+ # GitLab API returns 404 if there are no LDAP links configured.
143
+ refreshed_group = gl.groups.get(group.id)
144
+ with pytest.raises(GitlabListError):
145
+ refreshed_group.ldap_group_links.list(get_all=True)
@@ -4,6 +4,20 @@ import pytest
4
4
  from unittest.mock import patch, MagicMock
5
5
 
6
6
 
7
+ def test_get_group_id_excludes_projects():
8
+ from gitlabform.gitlab.core import GitLabCore
9
+
10
+ core = GitLabCore.__new__(GitLabCore)
11
+ core._make_requests_to_api = MagicMock(return_value={"id": "123"})
12
+
13
+ assert core._get_group_id("parent/group") == 123
14
+ core._make_requests_to_api.assert_called_once_with(
15
+ "groups/%s?with_projects=false",
16
+ "parent/group",
17
+ "GET",
18
+ )
19
+
20
+
7
21
  class TestGitLabCoreRetryConfiguration:
8
22
  """
9
23
  Tests for GitLabCore retry configuration.
@@ -0,0 +1,15 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ from gitlabform.gitlab.groups import GitLabGroups
4
+
5
+
6
+ def test_get_group_excludes_projects():
7
+ gitlab = GitLabGroups.__new__(GitLabGroups)
8
+ gitlab._make_requests_to_api = MagicMock(return_value={})
9
+
10
+ gitlab.get_group("parent/group")
11
+
12
+ gitlab._make_requests_to_api.assert_called_once_with(
13
+ "groups/%s?with_projects=false",
14
+ "parent/group",
15
+ )
@@ -8,6 +8,14 @@ from unittest.mock import MagicMock, patch
8
8
 
9
9
  class TestPythonGitlab:
10
10
 
11
+ def test_get_group_by_path_cached_excludes_projects(self):
12
+ python_gitlab = PythonGitlab(MagicMock())
13
+ group = MagicMock()
14
+ python_gitlab.groups.get = MagicMock(return_value=group)
15
+
16
+ assert python_gitlab.get_group_by_path_cached("parent/group") is group
17
+ python_gitlab.groups.get.assert_called_once_with("parent/group", with_projects=False)
18
+
11
19
  def test_get_member_role_id_cached_gets_role_id_from_roles_in_group_on_saas(self):
12
20
  group_name = "Test"
13
21
  role_name = "custom_role"
@@ -0,0 +1,165 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pytest
4
+ from gitlab.exceptions import GitlabCreateError, GitlabListError
5
+
6
+ from gitlabform.processors.group.group_ldap_links_processor import GroupLDAPLinksProcessor
7
+
8
+
9
+ class TestGroupLDAPLinksProcessor:
10
+ def setup_method(self):
11
+ with patch("gitlabform.processors.abstract_processor.GitlabWrapper"):
12
+ self.processor = GroupLDAPLinksProcessor(MagicMock())
13
+ self.gl = MagicMock()
14
+ self.processor.gl = self.gl
15
+
16
+ @staticmethod
17
+ def _link(provider: str = "LDAP Main", cn=None, filter=None, group_access: int = 30) -> MagicMock:
18
+ link = MagicMock()
19
+ link.provider = provider
20
+ link.cn = cn
21
+ link.filter = filter
22
+ link.group_access = group_access
23
+ link.asdict.return_value = {
24
+ "provider": provider,
25
+ "cn": cn,
26
+ "filter": filter,
27
+ "group_access": group_access,
28
+ }
29
+ return link
30
+
31
+ def _group(self, *links: MagicMock) -> MagicMock:
32
+ group = MagicMock()
33
+ group.ldap_group_links.list.return_value = list(links)
34
+ self.gl.get_group_by_path_cached.return_value = group
35
+ return group
36
+
37
+ def _process(self, group_ldap_links: dict) -> None:
38
+ self.processor._process_configuration("test/group", {"group_ldap_links": group_ldap_links})
39
+
40
+ # ------------------------------------------------------------------ add
41
+
42
+ def test_creates_link_when_missing(self):
43
+ group = self._group()
44
+ link_config = {"provider": "LDAP Main", "cn": "devops", "group_access": 40}
45
+
46
+ self._process({"devops_users": link_config})
47
+
48
+ group.ldap_group_links.create.assert_called_once_with(link_config)
49
+
50
+ def test_cn_link_does_not_match_filter_link(self):
51
+ # provider + cn and provider + filter are different identities
52
+ existing = self._link(cn="devops")
53
+ group = self._group(existing)
54
+ link_config = {"provider": "LDAP Main", "filter": "(devType=security)", "group_access": 30}
55
+
56
+ self._process({"security_users": link_config})
57
+
58
+ group.ldap_group_links.create.assert_called_once_with(link_config)
59
+ existing.delete.assert_not_called()
60
+
61
+ # --------------------------------------------------------------- update
62
+
63
+ def test_recreates_link_when_group_access_changed(self):
64
+ existing = self._link(cn="devops", group_access=40)
65
+ group = self._group(existing)
66
+ link_config = {"provider": "LDAP Main", "cn": "devops", "group_access": 30}
67
+
68
+ self._process({"devops_users": link_config})
69
+
70
+ # the API has no update endpoint for LDAP links, so it is deleted and added again
71
+ existing.delete.assert_called_once()
72
+ group.ldap_group_links.create.assert_called_once_with(link_config)
73
+
74
+ def test_no_churn_when_configured_link_matches_existing(self):
75
+ existing = self._link(cn="devops", group_access=40)
76
+ group = self._group(existing)
77
+
78
+ self._process({"devops_users": {"provider": "LDAP Main", "cn": "devops", "group_access": 40}})
79
+
80
+ existing.delete.assert_not_called()
81
+ group.ldap_group_links.create.assert_not_called()
82
+
83
+ # --------------------------------------------------------------- delete
84
+
85
+ def test_delete_true_removes_matching_link(self):
86
+ existing = self._link(cn="devops")
87
+ group = self._group(existing)
88
+
89
+ self._process({"devops_users": {"provider": "LDAP Main", "cn": "devops", "delete": True}})
90
+
91
+ existing.delete.assert_called_once()
92
+ group.ldap_group_links.create.assert_not_called()
93
+
94
+ def test_delete_true_on_non_existent_is_logged_and_skipped(self, caplog):
95
+ group = self._group()
96
+
97
+ with caplog.at_level("INFO"):
98
+ self._process({"devops_users": {"provider": "LDAP Main", "cn": "devops", "delete": True}})
99
+
100
+ matching = [r for r in caplog.records if "Not deleting devops_users of group_ldap_links" in r.message]
101
+ assert len(matching) == 1
102
+ group.ldap_group_links.create.assert_not_called()
103
+
104
+ # -------------------------------------------------------------- enforce
105
+
106
+ def test_enforce_deletes_unmanaged_links(self):
107
+ managed = self._link(cn="devops", group_access=40)
108
+ stray = self._link(filter="(devType=security)")
109
+ self._group(managed, stray)
110
+
111
+ self._process(
112
+ {
113
+ "enforce": True,
114
+ "devops_users": {"provider": "LDAP Main", "cn": "devops", "group_access": 40},
115
+ }
116
+ )
117
+
118
+ stray.delete.assert_called_once()
119
+ managed.delete.assert_not_called()
120
+
121
+ def test_without_enforce_unmanaged_links_are_left_alone(self):
122
+ stray = self._link(cn="stray")
123
+ self._group(stray)
124
+
125
+ self._process({})
126
+
127
+ stray.delete.assert_not_called()
128
+
129
+ # ------------------------------------------------------- empty list 404
130
+
131
+ def test_list_404_is_treated_as_no_links(self):
132
+ # GitLab returns 404 instead of an empty list when a group has no LDAP links
133
+ group = self._group()
134
+ group.ldap_group_links.list.side_effect = GitlabListError("404 Not Found", response_code=404)
135
+ link_config = {"provider": "LDAP Main", "cn": "devops", "group_access": 40}
136
+
137
+ self._process({"devops_users": link_config})
138
+
139
+ group.ldap_group_links.create.assert_called_once_with(link_config)
140
+
141
+ def test_other_list_error_is_reraised(self):
142
+ group = self._group()
143
+ group.ldap_group_links.list.side_effect = GitlabListError("403 Forbidden", response_code=403)
144
+
145
+ with pytest.raises(GitlabListError):
146
+ self._process({"devops_users": {"provider": "LDAP Main", "cn": "devops", "group_access": 40}})
147
+
148
+ group.ldap_group_links.create.assert_not_called()
149
+
150
+ # -------------------------------------------------------- create errors
151
+
152
+ def test_create_404_is_reraised_with_clearer_message(self):
153
+ # GitLab returns 404 instead of 400 when the create parameters are invalid
154
+ group = self._group()
155
+ group.ldap_group_links.create.side_effect = GitlabCreateError("404 Not Found", response_code=404)
156
+
157
+ with pytest.raises(GitlabCreateError, match="Invalid parameters"):
158
+ self._process({"devops_users": {"provider": "nonexistent", "cn": "devops", "group_access": 40}})
159
+
160
+ def test_other_create_error_is_reraised_unchanged(self):
161
+ group = self._group()
162
+ group.ldap_group_links.create.side_effect = GitlabCreateError("400 Bad Request", response_code=400)
163
+
164
+ with pytest.raises(GitlabCreateError, match="400 Bad Request"):
165
+ self._process({"devops_users": {"provider": "LDAP Main", "cn": "devops", "group_access": 40}})
@@ -17,15 +17,15 @@ wheels = [
17
17
 
18
18
  [[package]]
19
19
  name = "anyio"
20
- version = "4.13.0"
20
+ version = "4.14.2"
21
21
  source = { registry = "https://pypi.org/simple" }
22
22
  dependencies = [
23
23
  { name = "idna" },
24
24
  { name = "typing-extensions", marker = "python_full_version < '3.13'" },
25
25
  ]
26
- sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
26
+ sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
27
27
  wheels = [
28
- { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
28
+ { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
29
29
  ]
30
30
 
31
31
  [[package]]
@@ -555,7 +555,7 @@ wheels = [
555
555
 
556
556
  [[package]]
557
557
  name = "gitlabform"
558
- version = "6.2.2"
558
+ version = "6.2.4"
559
559
  source = { editable = "." }
560
560
  dependencies = [
561
561
  { name = "certifi" },
@@ -1939,11 +1939,11 @@ wheels = [
1939
1939
 
1940
1940
  [[package]]
1941
1941
  name = "urllib3"
1942
- version = "2.7.0"
1942
+ version = "2.8.0"
1943
1943
  source = { registry = "https://pypi.org/simple" }
1944
- sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
1944
+ sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
1945
1945
  wheels = [
1946
- { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
1946
+ { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
1947
1947
  ]
1948
1948
 
1949
1949
  [[package]]
@@ -1,40 +0,0 @@
1
- from gitlabform.gitlab.core import NotFoundException, InvalidParametersException
2
- from gitlabform.gitlab.groups import GitLabGroups
3
-
4
-
5
- class GitLabGroupLDAPLinks(GitLabGroups):
6
- def get_ldap_group_links(self, group):
7
- group_id = self.get_group_id_case_insensitive(group)
8
- return self._make_requests_to_api("groups/%s/ldap_group_links", group_id, expected_codes=[200, 404])
9
-
10
- def add_ldap_group_link(self, group, data):
11
- group_id = self.get_group_id_case_insensitive(group)
12
- data["id"] = group_id
13
-
14
- try:
15
- return self._make_requests_to_api(
16
- "groups/%s/ldap_group_links",
17
- group_id,
18
- method="POST",
19
- data=data,
20
- expected_codes=[200, 201],
21
- )
22
- # this is a GitLab API bug - it returns 404 here instead of 400 for bad requests...
23
- except NotFoundException:
24
- raise InvalidParametersException(f"Invalid parameters for a Group LDAP link for group {group}: {data}")
25
-
26
- def delete_ldap_group_link(self, group, data):
27
- if "group_access" in data:
28
- del data["group_access"]
29
-
30
- group_id = self.get_group_id_case_insensitive(group)
31
- data["id"] = group_id
32
-
33
- # 404 means that the LDAP group link is already removed, so let's accept it for idempotency
34
- self._make_requests_to_api(
35
- "groups/%s/ldap_group_links",
36
- group_id,
37
- method="DELETE",
38
- data=data,
39
- expected_codes=[204, 404],
40
- )