Coverage for src/gitlabracadabra/objects/project.py: 80%

116 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-07 22:55 +0200

1# 

2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com> 

3# 

4# This program is free software: you can redistribute it and/or modify 

5# it under the terms of the GNU Lesser General Public License as published by 

6# the Free Software Foundation, either version 3 of the License, or 

7# (at your option) any later version. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU Lesser General Public License for more details. 

13# 

14# You should have received a copy of the GNU Lesser General Public License 

15# along with this program. If not, see <http://www.gnu.org/licenses/>. 

16 

17import logging 

18from http import HTTPStatus 

19from typing import ClassVar 

20 

21from gitlab.exceptions import GitlabListError 

22 

23from gitlabracadabra.gitlab.access_levels import access_level_value 

24from gitlabracadabra.mixins.boards import BoardsMixin 

25from gitlabracadabra.mixins.groups import GroupsMixin 

26from gitlabracadabra.mixins.image_mirrors import ImageMirrorsMixin 

27from gitlabracadabra.mixins.labels import LabelsMixin 

28from gitlabracadabra.mixins.members import MembersMixin 

29from gitlabracadabra.mixins.milestones import MilestonesMixin 

30from gitlabracadabra.mixins.mirrors import MirrorsMixin 

31from gitlabracadabra.mixins.package_mirrors import PackageMirrorsMixin 

32from gitlabracadabra.mixins.pipeline_schedules import PipelineSchedulesMixin 

33from gitlabracadabra.mixins.protected_branches import ProtectedBranchesMixin 

34from gitlabracadabra.mixins.rename_branches import RenameBranchesMixin 

35from gitlabracadabra.mixins.variables import VariablesMixin 

36from gitlabracadabra.mixins.webhooks import WebhooksMixin 

37from gitlabracadabra.objects.object import GitLabracadabraObject 

38 

39logger = logging.getLogger(__name__) 

40 

41 

42class GitLabracadabraProject( 

43 BoardsMixin, 

44 GroupsMixin, 

45 ImageMirrorsMixin, 

46 LabelsMixin, 

47 MembersMixin, 

48 MilestonesMixin, 

49 MirrorsMixin, 

50 PackageMirrorsMixin, 

51 PipelineSchedulesMixin, 

52 ProtectedBranchesMixin, 

53 RenameBranchesMixin, 

54 VariablesMixin, 

55 WebhooksMixin, 

56 GitLabracadabraObject, 

57): 

58 EXAMPLE_YAML_HEADER: ClassVar[str] = "mygroup/myproject:\n" 

59 DOC: ClassVar[list[str]] = [ 

60 "# Project lifecycle", 

61 "gitlab_id", 

62 "create_object", 

63 "delete_object", 

64 "initialize_with_readme", 

65 "repository_object_format", 

66 "# Manage", 

67 "## Members", 

68 "members", 

69 "unknown_members", 

70 "groups", 

71 "unknown_groups", 

72 "## Labels", 

73 "labels", 

74 "unknown_labels", 

75 "# Plan", 

76 "## Issue boards", 

77 "boards", 

78 "unknown_boards", 

79 "unknown_board_lists", 

80 "## Milestones", 

81 "milestones", 

82 "unknown_milestones", 

83 # '## Iterations', 

84 "# Code", 

85 "## Branches", 

86 "branches", 

87 "rename_branches", 

88 "# Build", 

89 "## Pipeline schedules", 

90 "pipeline_schedules", 

91 "unknown_pipeline_schedules", 

92 "unknown_pipeline_schedule_variables", 

93 "# Settings", 

94 "## General Settings", 

95 "### Naming, description, topics", 

96 "name", 

97 "description", 

98 "topics", 

99 # 'avatar', # FIXME: Gitlabracadabra 

100 "### Visibility, project features, permissions", 

101 "visibility", 

102 "request_access_enabled", 

103 "issues_access_level", 

104 # 'cve_id_request_enabled', # FIXME: GitLab 

105 "repository_access_level", 

106 "merge_requests_access_level", 

107 "forking_access_level", 

108 "lfs_enabled", 

109 "builds_access_level", 

110 "container_registry_access_level", 

111 "analytics_access_level", 

112 "requirements_access_level", 

113 "security_and_compliance_access_level", 

114 "wiki_access_level", 

115 "snippets_access_level", 

116 "packages_enabled", 

117 "model_experiments_access_level", 

118 "model_registry_access_level", 

119 "pages_access_level", 

120 "monitor_access_level", 

121 "environments_access_level", 

122 "feature_flags_access_level", 

123 "infrastructure_access_level", 

124 "releases_access_level", 

125 # 'duo_features_enabled', # FIXME: Gitlab 

126 "emails_enabled", 

127 # 'show_diff_preview_in_email', # FIXME: Gitlab 

128 # 'show_default_award_emojis', # FIXME: Gitlab 

129 # 'ci_resources_enabled', # FIXME: Gitlab 

130 # '### Badges', 

131 "### Default description template for issues", 

132 "issues_template", 

133 "### Service Desk", 

134 "service_desk_enabled", 

135 # 'service_desk_...', # FIXME: Gitlab 

136 "### Advanced", 

137 "archived", 

138 # '## Integrations', # FIXME: section 

139 "## Webhooks", 

140 "webhooks", 

141 "unknown_webhooks", 

142 "## Repository", 

143 "### Branch defaults", 

144 "default_branch", 

145 "autoclose_referenced_issues", 

146 "issue_branch_template", 

147 # '### Branch Rules', # FIXME: ... 

148 # '### Push Rules', # FIXME: ... 

149 "### Mirroring repositories", 

150 "mirror", 

151 "import_url", 

152 "mirror_user_id", 

153 "mirror_overwrites_diverged_branches", 

154 "mirror_trigger_builds", 

155 "only_mirror_protected_branches", 

156 "### Protected Branches", 

157 "protected_branches", 

158 "unknown_protected_branches", 

159 "### Protected Tags", 

160 "protected_tags", 

161 "unknown_protected_tags", 

162 # '### Deploy Keys', # FIXME: ... 

163 # '### Deploy Tokens', # FIXME: ... 

164 "## Merge requests", 

165 "### Merge requests", 

166 "merge_method", 

167 "merge_pipelines_enabled", 

168 "merge_trains_enabled", 

169 # 'merge_trains_skip_train_allowed', # FIXME: GitLab 

170 "resolve_outdated_diff_discussions", 

171 "printing_merge_request_link_enabled", 

172 "remove_source_branch_after_merge", 

173 "squash_option", 

174 "only_allow_merge_if_pipeline_succeeds", 

175 "allow_merge_on_skipped_pipeline", 

176 "only_allow_merge_if_all_discussions_are_resolved", 

177 "only_allow_merge_if_all_status_checks_passed", 

178 "suggestion_commit_message", 

179 "merge_commit_template", 

180 "squash_commit_template", 

181 "merge_requests_template", 

182 "### Merge request approvals", # FIXME: ... 

183 "approvals_before_merge", 

184 "## CI / CD Settings", 

185 "### General pipelines", 

186 "public_jobs", 

187 "auto_cancel_pending_pipelines", 

188 "ci_forward_deployment_enabled", 

189 "ci_forward_deployment_rollback_allowed", 

190 "ci_separated_caches", 

191 "ci_restrict_pipeline_cancellation_role", 

192 "ci_config_path", 

193 "build_git_strategy", 

194 "ci_default_git_depth", 

195 "build_timeout", 

196 "ci_allow_fork_pipelines_to_run_in_parent_project", 

197 "### Auto DevOps", 

198 "auto_devops_enabled", 

199 "auto_devops_deploy_strategy", 

200 "### Protected Environments", 

201 # FIXME: ... 

202 "allow_pipeline_trigger_approve_deployment", 

203 "### Runners", 

204 "shared_runners_enabled", 

205 "group_runners_enabled", 

206 "### Artifacts", 

207 "keep_latest_artifact", 

208 "### Variables", 

209 "variables", 

210 "unknown_variables", 

211 # '### Pipeline trigger tokens', # FIXME: section 

212 # '### Automatic deployment rollbacks', # FIXME: section 

213 # 'auto_rollback_enabled', # FIXME: GitLab 

214 # '### Deploy freezes', # FIXME: section 

215 # '### Job token permissions', # FIXME: section 

216 # '### Secure files', # FIXME: section 

217 # '### Pipeline subscriptions', # FIXME: section 

218 "## Packages and registries", 

219 "### Cleanup policies", 

220 "container_expiration_policy", 

221 "# Mirroring repositories, packages and container images", 

222 "mirrors", 

223 "package_mirrors", 

224 "image_mirrors", 

225 "# Deprecated", 

226 "build_coverage_regex", 

227 "container_registry_enabled", 

228 "emails_disabled", 

229 "issues_enabled", 

230 "jobs_enabled", 

231 "merge_requests_enabled", 

232 "public_builds", 

233 "snippets_enabled", 

234 "tag_list", 

235 "wiki_enabled", 

236 ] 

237 SCHEMA: ClassVar[dict] = { 

238 "$schema": "http://json-schema.org/draft-04/schema#", 

239 "title": "Project", 

240 "type": "object", 

241 "properties": { 

242 "gitlab_id": { 

243 "type": "string", 

244 "description": "GitLab id", 

245 "_example": "gitlab", 

246 "_doc_link": "action_file.md#gitlab_id", 

247 }, 

248 "create_object": { 

249 "type": "boolean", 

250 "description": "Create object if it does not exists", 

251 }, 

252 "delete_object": { 

253 "type": "boolean", 

254 "description": "Delete object if it exists", 

255 }, 

256 # From https://docs.gitlab.com/ee/api/projects.html#create-project 

257 # and https://docs.gitlab.com/ee/api/projects.html#edit-project 

258 "initialize_with_readme": { 

259 "type": "boolean", 

260 "description": "false by default", 

261 }, 

262 "repository_object_format": { 

263 "type": "string", 

264 "description": "Repository object format", 

265 "enum": ["sha1", "sha256"], 

266 }, 

267 # From https://docs.gitlab.com/ee/api/members.html#add-a-member-to-a-group-or-project 

268 # FIXME: expires_at not supported 

269 "members": { 

270 "type": "object", 

271 "description": "Members", 

272 "additionalProperties": { 

273 "type": "string", 

274 "description": "The permissions level to grant the member.", 

275 "enum": ["guest", "reporter", "developer", "maintainer", "owner"], 

276 }, 

277 "_example": ( 

278 "\n foo: developer\n bar: maintainer # one of guest, reporter, developer, maintainer, owner\n" 

279 ), 

280 }, 

281 "unknown_members": { # GitLabracadabra 

282 "type": "string", 

283 "description": "What to do with unknown members (`warn` by default).", 

284 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

285 }, 

286 # From https://docs.gitlab.com/ee/api/projects.html#share-project-with-group_access_level 

287 # and https://docs.gitlab.com/ee/api/projects.html#delete-a-shared-project-link-within-a-group 

288 # FIXME: expires_at not supported 

289 "groups": { 

290 "type": "object", 

291 "description": "Groups", 

292 "additionalProperties": { 

293 "type": "string", 

294 "description": "The permissions level to grant the group.", 

295 "enum": ["guest", "reporter", "developer", "maintainer"], 

296 }, 

297 "_example": ( 

298 "\n group/foo: guest\n group/bar: reporter # one of guest, reporter, developer, maintainer\n" 

299 ), 

300 }, 

301 "unknown_groups": { # GitLabracadabra 

302 "type": "string", 

303 "description": "What to do with unknown groups (`warn` by default).", 

304 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

305 }, 

306 # From https://docs.gitlab.com/ee/api/labels.html#create-a-new-label 

307 "labels": { 

308 "type": "array", 

309 "description": "The list of project's labels", 

310 "items": { 

311 "type": "object", 

312 "properties": { 

313 "name": { 

314 "type": "string", 

315 "description": "The name of the label.", 

316 }, 

317 "color": { 

318 "type": "string", 

319 "description": "The color of the label.", 

320 }, 

321 "description": { 

322 "type": "string", 

323 "description": "The description of the label.", 

324 }, 

325 "priority": { 

326 "type": "integer", 

327 "description": "The priority of the label.", 

328 }, 

329 }, 

330 "required": ["name"], # color not required to allow priority override 

331 "additionalProperties": False, 

332 }, 

333 "uniqueItems": True, 

334 "_example": ( 

335 "\n" 

336 " - name: critical\n" 

337 " priority: 0\n" 

338 " - name: bug\n" 

339 " priority: 1\n" 

340 " - name: confirmed\n" 

341 " priority: 2\n" 

342 ), 

343 }, 

344 "unknown_labels": { # GitLabracadabra 

345 "type": "string", 

346 "description": "What to do with unknown labels (`warn` by default).", 

347 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

348 }, 

349 # From https://docs.gitlab.com/ee/api/boards.html 

350 "boards": { 

351 "type": "array", 

352 "description": "The list of project's boards", 

353 "items": { 

354 "type": "object", 

355 "properties": { 

356 "name": { 

357 "type": "string", 

358 "description": "The name of the board.", 

359 }, 

360 "old_name": { 

361 "type": "string", 

362 "description": "The previous name of the board.", 

363 }, 

364 "hide_backlog_list": { 

365 "type": "boolean", 

366 "description": "Hide the Open list", 

367 }, 

368 "hide_closed_list": { 

369 "type": "boolean", 

370 "description": "Hide the Closed list", 

371 }, 

372 "lists": { 

373 "type": "array", 

374 "description": "Ordered list of labels", 

375 "items": { 

376 "type": "object", 

377 "properties": { 

378 "label": { 

379 "type": "string", 

380 "description": "The name of a label", 

381 }, 

382 }, 

383 }, 

384 }, 

385 "unknown_lists": { # GitLabracadabra 

386 "type": "string", 

387 "description": ( 

388 "What to do with unknown board lists (Value of `unknown_board_lists` by default)." 

389 ), 

390 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

391 }, 

392 }, 

393 "required": ["name"], 

394 "additionalProperties": False, 

395 }, 

396 "uniqueItems": True, 

397 "_example": ( 

398 "\n" 

399 " - name: My Board\n" 

400 " # old_name: Development # Use this to rename a board\n" 

401 " hide_backlog_list: false\n" 

402 " hide_closed_list: false\n" 

403 " lists:\n" 

404 " - label: TODO\n" 

405 " - label: WIP\n" 

406 ), 

407 }, 

408 "unknown_boards": { # GitLabracadabra 

409 "type": "string", 

410 "description": "What to do with unknown boards (`warn` by default).", 

411 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

412 }, 

413 "unknown_board_lists": { # GitLabracadabra 

414 "type": "string", 

415 "description": "What to do with unknown board lists (`delete` by default).", 

416 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

417 }, 

418 # From https://docs.gitlab.com/ee/api/milestones.html#edit-milestone 

419 "milestones": { 

420 "type": "array", 

421 "description": "The list of project's milestones", 

422 "items": { 

423 "type": "object", 

424 "properties": { 

425 "title": { 

426 "type": "string", 

427 "description": "The title of a milestone", 

428 }, 

429 "description": { 

430 "type": "string", 

431 "description": "The description of a milestone", 

432 }, 

433 "due_date": { 

434 "type": "string", 

435 "description": "The due date of the milestone", 

436 "pattern": "^(\\d{4}-\\d{2}-\\d{2})?$", 

437 }, 

438 "start_date": { 

439 "type": "string", 

440 "description": "The start date of the milestone", 

441 "pattern": "^(\\d{4}-\\d{2}-\\d{2})?$", 

442 }, 

443 "state": { 

444 "type": "string", 

445 "description": "The state event of the milestone", 

446 "enum": ["closed", "active"], 

447 }, 

448 }, 

449 "required": ["title"], 

450 "additionalProperties": False, 

451 }, 

452 "uniqueItems": True, 

453 "_example": ( 

454 "\n" 

455 " - title: '1.0'\n" 

456 " description: Version 1.0\n" 

457 " due_date: '2021-01-23' # Quotes are mandatory\n" 

458 " start_date: '2020-01-23' # Quotes are mandatory\n" 

459 " state: active # or closed\n" 

460 ), 

461 }, 

462 "unknown_milestones": { # GitLabracadabra 

463 "type": "string", 

464 "description": "What to do with unknown milestones (`warn` by default).", 

465 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

466 }, 

467 # From https://docs.gitlab.com/ee/api/branches.html#create-repository-branch 

468 "branches": { 

469 "type": "array", 

470 "description": "The list of branches for a project. Branches are created in order", 

471 "items": { 

472 "type": "string", 

473 }, 

474 "uniqueItems": True, 

475 "_example": ("\n - main\n - develop"), 

476 }, 

477 "rename_branches": { 

478 "type": "array", 

479 "description": "Rename branches of a project. Rename pairs (old_name: new_name) are processed in order", 

480 "items": { 

481 "type": "object", 

482 "additionalProperties": { 

483 "type": "string", 

484 "description": "The new branch name.", 

485 }, 

486 "minProperties": 1, 

487 "maxProperties": 1, 

488 }, 

489 "uniqueItems": True, 

490 "_example": ( 

491 "\n" 

492 " - old_name: new_name\n" 

493 " # To Rename consecutive branches:\n" 

494 " - branch2: branch3\n" 

495 " - branch1: branch2" 

496 ), 

497 }, 

498 # From https://docs.gitlab.com/ee/api/pipeline_schedules.html#create-a-new-pipeline-schedule 

499 "pipeline_schedules": { 

500 "type": "array", 

501 "description": "The list of project's pipeline schedules", 

502 "items": { 

503 "type": "object", 

504 "properties": { 

505 "description": { 

506 "type": "string", 

507 "description": "The description of pipeline schedule", 

508 "pattern": "[a-zA-Z0-9_]+", 

509 }, 

510 "ref": { 

511 "type": "string", 

512 "description": "The branch/tag name will be triggered", 

513 }, 

514 "cron": { 

515 "type": "string", 

516 "description": ( 

517 "The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron))" 

518 ), 

519 }, 

520 "cron_timezone": { 

521 "type": "string", 

522 "description": ( 

523 "The timezone supported by `ActiveSupport::TimeZone` " 

524 "(e.g. `Pacific Time (US & Canada)`) (default: `'UTC'`)" 

525 ), 

526 }, 

527 "active": { 

528 "type": "boolean", 

529 "description": "The activation of pipeline schedule", 

530 }, 

531 # From https://docs.gitlab.com/ee/api/pipeline_schedules.html 

532 # #create-a-new-pipeline-schedule-variable 

533 "variables": { 

534 "type": "array", 

535 "description": "The list of project's variables", 

536 "items": { 

537 "type": "object", 

538 "properties": { 

539 "key": { 

540 "type": "string", 

541 "description": "The key of a variable", 

542 "pattern": "[a-zA-Z0-9_]+", 

543 }, 

544 "value": { 

545 "type": "string", 

546 "description": "The value of a variable", 

547 }, 

548 "variable_type": { 

549 "type": "string", 

550 "description": ( 

551 "The type of a variable. Available types are: env_var (default) and file" 

552 ), 

553 "enum": ["env_var", "file"], 

554 }, 

555 }, 

556 "required": ["key", "value"], 

557 "additionalProperties": False, 

558 }, 

559 "uniqueItems": True, 

560 }, 

561 "unknown_variables": { # GitLabracadabra 

562 "type": "string", 

563 "description": ( 

564 "What to do with unknown pipeline schedule variables " 

565 "(Value of `unknown_pipeline_schedule_variables` by default)." 

566 ), 

567 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

568 }, 

569 }, 

570 "required": ["description", "ref", "cron"], 

571 "additionalProperties": False, 

572 }, 

573 "uniqueItems": True, 

574 "_example": ( 

575 "\n" 

576 " - description: Build packages\n" 

577 " ref: main\n" 

578 " cron: '0 1 * * 5'\n" 

579 " # cron_timezone: UTC\n" 

580 " # active: true\n" 

581 " variables:\n" 

582 " - key: MY_VAR\n" 

583 " value: my value\n" 

584 " # variable_type: env_var # or file\n" 

585 " # unknown_variables: warn # one of warn, delete, remove, ignore, skip\n" 

586 ), 

587 }, 

588 "unknown_pipeline_schedules": { # GitLabracadabra 

589 "type": "string", 

590 "description": "What to do with unknown pipeline schedules (`warn` by default).", 

591 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

592 }, 

593 "unknown_pipeline_schedule_variables": { # GitLabracadabra 

594 "type": "string", 

595 "description": "What to do with unknown pipeline schedule variables (`warn` by default).", 

596 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

597 }, 

598 "name": { 

599 "type": "string", 

600 "description": "Project name", 

601 }, 

602 # 'path': { 

603 # 'type': 'string', 

604 # 'description': 'Repository name for new project. ' 

605 # 'Generated based on name if not provided (generated lowercased with dashes).', 

606 # }, 

607 "description": { 

608 "type": "string", 

609 "description": "Project description", 

610 "_example": "|-\n 🧹 GitLabracadabra 🧙\n\n :alembic: Adds some magic to GitLab :crystal\\_ball:", 

611 }, 

612 "topics": { 

613 "type": "array", 

614 "description": "Topics", 

615 "items": { 

616 "type": "string", 

617 }, 

618 "uniqueItems": True, 

619 "_example": "[GitLab, API, YAML]", 

620 }, 

621 # 'avatar': { 

622 # 'type': 'string', 

623 # 'description': 'Project avatar', 

624 # }, 

625 "visibility": { 

626 "type": "string", 

627 "description": "Project visibility", 

628 "enum": ["private", "internal", "public"], 

629 }, 

630 "request_access_enabled": { 

631 "type": "boolean", 

632 "description": "Allow users to request access", 

633 }, 

634 "issues_access_level": { 

635 "type": "string", 

636 "description": "Set visibility of issues.", 

637 "enum": ["disabled", "private", "enabled"], 

638 }, 

639 "repository_access_level": { 

640 "type": "string", 

641 "description": "Set visibility of repository.", 

642 "enum": ["disabled", "private", "enabled"], 

643 }, 

644 "merge_requests_access_level": { 

645 "type": "string", 

646 "description": "Set visibility of merge requests.", 

647 "enum": ["disabled", "private", "enabled"], 

648 }, 

649 "forking_access_level": { 

650 "type": "string", 

651 "description": "Set visibility of forks.", 

652 "enum": ["disabled", "private", "enabled"], 

653 }, 

654 "lfs_enabled": { 

655 "type": "boolean", 

656 "description": "Enable LFS", 

657 }, 

658 "builds_access_level": { 

659 "type": "string", 

660 "description": "Set visibility of pipelines.", 

661 "enum": ["disabled", "private", "enabled"], 

662 }, 

663 "container_registry_access_level": { 

664 "type": "string", 

665 "description": "Set visibility of container registry.", 

666 "enum": ["disabled", "private", "enabled"], 

667 }, 

668 "analytics_access_level": { 

669 "type": "string", 

670 "description": "Set visibility of analytics.", 

671 "enum": ["disabled", "private", "enabled"], 

672 }, 

673 "requirements_access_level": { 

674 "type": "string", 

675 "description": "Set visibility of requirements management.", 

676 "enum": ["disabled", "private", "enabled"], 

677 }, 

678 "security_and_compliance_access_level": { 

679 "type": "string", 

680 "description": "Set visibility of security and compliance.", 

681 "enum": ["disabled", "private", "enabled"], 

682 }, 

683 "wiki_access_level": { 

684 "type": "string", 

685 "description": "Set visibility of wiki.", 

686 "enum": ["disabled", "private", "enabled"], 

687 }, 

688 "snippets_access_level": { 

689 "type": "string", 

690 "description": "Set visibility of snippets.", 

691 "enum": ["disabled", "private", "enabled"], 

692 }, 

693 "packages_enabled": { 

694 "type": "boolean", 

695 "description": "Enable or disable packages repository feature", 

696 }, 

697 "model_experiments_access_level": { 

698 "type": "string", 

699 "description": "Set visibility of machine learning model experiments.", 

700 "enum": ["disabled", "private", "enabled"], 

701 }, 

702 "model_registry_access_level": { 

703 "type": "string", 

704 "description": "Set visibility of machine learning model registry.", 

705 "enum": ["disabled", "private", "enabled"], 

706 }, 

707 "pages_access_level": { 

708 "type": "string", 

709 "description": "Set visibility of GitLab Pages.", 

710 "enum": ["disabled", "private", "enabled", "public"], 

711 }, 

712 "monitor_access_level": { 

713 "type": "string", 

714 "description": "Set visibility of application performance monitoring.", 

715 "enum": ["disabled", "private", "enabled"], 

716 }, 

717 "environments_access_level": { 

718 "type": "string", 

719 "description": "Set visibility of environments.", 

720 "enum": ["disabled", "private", "enabled"], 

721 }, 

722 "feature_flags_access_level": { 

723 "type": "string", 

724 "description": "Set visibility of feature flags.", 

725 "enum": ["disabled", "private", "enabled"], 

726 }, 

727 "infrastructure_access_level": { 

728 "type": "string", 

729 "description": "Set visibility of infrastructure management.", 

730 "enum": ["disabled", "private", "enabled"], 

731 }, 

732 "releases_access_level": { 

733 "type": "string", 

734 "description": "Set visibility of releases.", 

735 "enum": ["disabled", "private", "enabled"], 

736 }, 

737 "emails_enabled": { 

738 "type": "boolean", 

739 "description": "Enable email notifications.", 

740 }, 

741 "issues_template": { 

742 "type": "string", 

743 "description": "Default description for Issues.", 

744 }, 

745 "service_desk_enabled": { 

746 "type": "boolean", 

747 "description": "Enable or disable Service Desk feature.", 

748 }, 

749 # https://docs.gitlab.com/ee/api/projects.html#archive-a-project 

750 # https://docs.gitlab.com/ee/api/projects.html#unarchive-a-project 

751 "archived": { 

752 "type": "boolean", 

753 "description": "Archive or unarchive project", 

754 }, 

755 # From https://docs.gitlab.com/ee/api/projects.html#hooks 

756 "webhooks": { 

757 "type": "array", 

758 "description": "The list of project's webhooks", 

759 "items": { 

760 "type": "object", 

761 "properties": { 

762 "url": { 

763 "type": "string", 

764 "description": "The hook URL", 

765 }, 

766 "push_events": { 

767 "type": "boolean", 

768 "description": "Trigger hook on push events", 

769 }, 

770 "push_events_branch_filter": { 

771 "type": "string", 

772 "description": "Trigger hook on push events for matching branches only", 

773 }, 

774 "issues_events": { 

775 "type": "boolean", 

776 "description": "Trigger hook on issues events", 

777 }, 

778 "confidential_issues_events": { 

779 "type": "boolean", 

780 "description": "Trigger hook on confidential issues events", 

781 }, 

782 "merge_requests_events": { 

783 "type": "boolean", 

784 "description": "Trigger hook on merge requests events", 

785 }, 

786 "tag_push_events": { 

787 "type": "boolean", 

788 "description": "Trigger hook on tag push events", 

789 }, 

790 "note_events": { 

791 "type": "boolean", 

792 "description": "Trigger hook on note events", 

793 }, 

794 "confidential_note_events": { 

795 "type": "boolean", 

796 "description": "Trigger hook on confidential note events", 

797 }, 

798 "job_events": { 

799 "type": "boolean", 

800 "description": "Trigger hook on job events", 

801 }, 

802 "pipeline_events": { 

803 "type": "boolean", 

804 "description": "Trigger hook on pipeline events", 

805 }, 

806 "wiki_page_events": { 

807 "type": "boolean", 

808 "description": "Trigger hook on wiki events", 

809 }, 

810 "enable_ssl_verification": { 

811 "type": "boolean", 

812 "description": "Do SSL verification when triggering the hook", 

813 }, 

814 "token": { 

815 "type": "string", 

816 "description": ( 

817 "Secret token to validate received payloads; this will not be returned in the response" 

818 ), 

819 }, 

820 }, 

821 "required": ["url"], 

822 "additionalProperties": False, 

823 }, 

824 "uniqueItems": True, 

825 "_example": ( 

826 "\n" 

827 " - url: http://example.com/api/trigger\n" 

828 " push_events: true\n" 

829 " push_events_branch_filter: ''\n" 

830 " issues_events: true\n" 

831 " confidential_issues_events: true\n" 

832 " merge_requests_events: true\n" 

833 " tag_push_events: true\n" 

834 " note_events: true\n" 

835 " confidential_note_events: true\n" 

836 " job_events: true\n" 

837 " pipeline_events: true\n" 

838 " wiki_page_events: true\n" 

839 " enable_ssl_verification: true\n" 

840 " # token: T0k3N\n" 

841 ), 

842 }, 

843 "unknown_webhooks": { # GitLabracadabra 

844 "type": "string", 

845 "description": "What to do with unknown webhooks (`warn` by default).", 

846 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

847 }, 

848 "default_branch": { 

849 "type": "string", 

850 "description": "The default branch name", 

851 }, 

852 "autoclose_referenced_issues": { 

853 "type": "boolean", 

854 "description": "Set whether auto-closing referenced issues on default branch", 

855 }, 

856 "issue_branch_template": { 

857 "type": "string", 

858 "description": "Template used to suggest names for branches created from issues.", 

859 }, 

860 # From https://docs.gitlab.com/ee/api/projects.html#edit-project 

861 "mirror": { 

862 "type": "boolean", 

863 "description": "Enables pull mirroring in a project", 

864 }, 

865 "import_url": { 

866 "type": "string", 

867 "description": "URL to import repository from", 

868 }, 

869 "mirror_user_id": { 

870 "type": "integer", 

871 "description": "User responsible for all the activity surrounding a pull mirror event", 

872 }, 

873 "mirror_overwrites_diverged_branches": { 

874 "type": "boolean", 

875 "description": "Pull mirror overwrites diverged branches", 

876 }, 

877 "mirror_trigger_builds": { 

878 "type": "boolean", 

879 "description": "Pull mirroring triggers builds", 

880 }, 

881 "only_mirror_protected_branches": { 

882 "type": "boolean", 

883 "description": "Only mirror protected branches", 

884 }, 

885 # From https://docs.gitlab.com/ee/api/protected_branches.html#protect-repository-branches 

886 "protected_branches": { 

887 "type": "object", 

888 "description": "Protected branches", 

889 "additionalProperties": { 

890 "type": "object", 

891 "properties": { 

892 "merge_access_level": { 

893 "type": "string", 

894 "description": "Access levels allowed to merge.", 

895 "enum": ["noone", "developer", "maintainer", "admin"], 

896 }, 

897 "allowed_to_merge": { 

898 "type": "array", 

899 "description": "", 

900 "items": { 

901 "oneOf": [ 

902 { 

903 "type": "object", 

904 "description": "Role", 

905 "properties": { 

906 "role": { 

907 "type": "string", 

908 "description": "Role name", 

909 "enum": ["noone", "developer", "maintainer", "admin"], 

910 }, 

911 }, 

912 "additionalProperties": False, 

913 }, 

914 { 

915 "type": "object", 

916 "description": "User", 

917 "properties": { 

918 "user": { 

919 "type": "string", 

920 "description": "User name", 

921 }, 

922 }, 

923 "additionalProperties": False, 

924 }, 

925 { 

926 "type": "object", 

927 "description": "Group", 

928 "properties": { 

929 "group": { 

930 "type": "string", 

931 "description": "Group full path", 

932 }, 

933 }, 

934 "additionalProperties": False, 

935 }, 

936 ], 

937 }, 

938 }, 

939 "push_access_level": { 

940 "type": "string", 

941 "description": "Access levels allowed to push.", 

942 "enum": ["noone", "developer", "maintainer", "admin"], 

943 }, 

944 "allowed_to_push": { 

945 "type": "array", 

946 "description": "", 

947 "items": { 

948 "oneOf": [ 

949 { 

950 "type": "object", 

951 "description": "Role", 

952 "properties": { 

953 "role": { 

954 "type": "string", 

955 "description": "Role name", 

956 "enum": ["noone", "developer", "maintainer", "admin"], 

957 }, 

958 }, 

959 "additionalProperties": False, 

960 }, 

961 { 

962 "type": "object", 

963 "description": "User", 

964 "properties": { 

965 "user": { 

966 "type": "string", 

967 "description": "User name", 

968 }, 

969 }, 

970 "additionalProperties": False, 

971 }, 

972 { 

973 "type": "object", 

974 "description": "Group", 

975 "properties": { 

976 "group": { 

977 "type": "string", 

978 "description": "Group full path", 

979 }, 

980 }, 

981 "additionalProperties": False, 

982 }, 

983 { 

984 "type": "object", 

985 "description": "Deploy Key", 

986 "properties": { 

987 "deploy_key": { 

988 "type": "string", 

989 "description": "Deploy key Title", 

990 }, 

991 }, 

992 "additionalProperties": False, 

993 }, 

994 ], 

995 }, 

996 }, 

997 "allow_force_push": { 

998 "type": "boolean", 

999 "description": "When enabled, members who can push to this branch can also force push.", 

1000 }, 

1001 "code_owner_approval_required": { 

1002 "type": "boolean", 

1003 "description": ( 

1004 "Prevent pushes to this branch if it matches an item in the CODEOWNERS file." 

1005 ), 

1006 }, 

1007 "allowed_to_unprotect": { 

1008 "type": "array", 

1009 "description": "", 

1010 "items": { 

1011 "oneOf": [ 

1012 { 

1013 "type": "object", 

1014 "description": "Role", 

1015 "properties": { 

1016 "role": { 

1017 "type": "string", 

1018 "description": "Role name", 

1019 "enum": ["developer", "maintainer", "admin"], 

1020 }, 

1021 }, 

1022 "additionalProperties": False, 

1023 }, 

1024 { 

1025 "type": "object", 

1026 "description": "User", 

1027 "properties": { 

1028 "user": { 

1029 "type": "string", 

1030 "description": "User name", 

1031 }, 

1032 }, 

1033 "additionalProperties": False, 

1034 }, 

1035 { 

1036 "type": "object", 

1037 "description": "Group", 

1038 "properties": { 

1039 "group": { 

1040 "type": "string", 

1041 "description": "Group full path", 

1042 }, 

1043 }, 

1044 "additionalProperties": False, 

1045 }, 

1046 ], 

1047 }, 

1048 }, 

1049 }, 

1050 }, 

1051 "_example": ( 

1052 "\n" 

1053 " main:\n" 

1054 " allowed_to_merge::\n" 

1055 " - role: developer # one of noone, developer, maintainer, admin\n" 

1056 " - user: my_username # EE only\n" 

1057 " - group: my_group # EE only\n" 

1058 " allowed_to_push::\n" 

1059 " - role: noone # one of noone, developer, maintainer, admin\n" 

1060 " - user: my_username # EE only\n" 

1061 " - group: my_group # EE only\n" 

1062 " - deploy_key: Deploy Key Title # EE only\n" 

1063 " allow_force_push: false\n" 

1064 " code_owner_approval_required: false # EE only\n" 

1065 " allowed_to_unprotect: # EE only\n" 

1066 " - role: maintainer # one of developer, maintainer, admin\n" 

1067 " - user: my_username\n" 

1068 " - group: my_group\n" 

1069 " develop:\n" 

1070 " merge_access_level: developer\n" 

1071 " push_access_level: developer\n" 

1072 ), 

1073 }, 

1074 "unknown_protected_branches": { # GitLabracadabra 

1075 "type": "string", 

1076 "description": "What to do with unknown protected branches (`warn` by default).", 

1077 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

1078 }, 

1079 # From https://docs.gitlab.com/ee/api/protected_tags.html#protect-repository-tags 

1080 "protected_tags": { 

1081 "type": "object", 

1082 "description": "Protected tags", 

1083 "additionalProperties": { 

1084 "type": "string", 

1085 "description": "Access levels allowed to create (defaults: maintainer access level)", 

1086 "enum": ["noone", "developer", "maintainer"], 

1087 }, 

1088 "_example": ( 

1089 "\n" 

1090 " v*: maintainer # one of noone, developer, maintainer\n" 

1091 " '*': developer # one of noone, developer, maintainer\n" 

1092 ), 

1093 }, 

1094 "unknown_protected_tags": { # GitLabracadabra 

1095 "type": "string", 

1096 "description": "What to do with unknown protected tags (`warn` by default).", 

1097 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

1098 }, 

1099 "merge_method": { 

1100 "type": "string", 

1101 "description": "Set the merge method used", 

1102 "enum": ["merge", "rebase_merge", "ff"], 

1103 }, 

1104 "merge_pipelines_enabled": { 

1105 "type": "boolean", 

1106 "description": "Enable or disable merged results pipelines.", 

1107 }, 

1108 "merge_trains_enabled": { 

1109 "type": "boolean", 

1110 "description": "Enable or disable merge trains.", 

1111 }, 

1112 "resolve_outdated_diff_discussions": { 

1113 "type": "boolean", 

1114 "description": "Automatically resolve merge request diffs discussions on lines changed with a push", 

1115 }, 

1116 "printing_merge_request_link_enabled": { 

1117 "type": "boolean", 

1118 "description": "Show link to create/view merge request when pushing from the command line", 

1119 }, 

1120 "remove_source_branch_after_merge": { 

1121 "type": "boolean", 

1122 "description": "Enable Delete source branch option by default for all new merge requests", 

1123 }, 

1124 "squash_option": { 

1125 "type": "string", 

1126 "description": "Squash option.", 

1127 "enum": ["never", "always", "default_on", "default_off"], 

1128 }, 

1129 "only_allow_merge_if_pipeline_succeeds": { 

1130 "type": "boolean", 

1131 "description": "Set whether merge requests can only be merged with successful jobs", 

1132 }, 

1133 "allow_merge_on_skipped_pipeline": { 

1134 "type": "boolean", 

1135 "description": "Set whether or not merge requests can be merged with skipped jobs", 

1136 }, 

1137 "only_allow_merge_if_all_discussions_are_resolved": { 

1138 "type": "boolean", 

1139 "description": "Set whether merge requests can only be merged when all the discussions are resolved", 

1140 }, 

1141 "only_allow_merge_if_all_status_checks_passed": { 

1142 "type": "boolean", 

1143 "description": ( 

1144 "Indicates that merges of merge requestsshould be blocked unless all status checks have passed." 

1145 ), 

1146 }, 

1147 "suggestion_commit_message": { 

1148 "type": "string", 

1149 "description": "The commit message used to apply merge request suggestions", 

1150 }, 

1151 "merge_commit_template": { 

1152 "type": "string", 

1153 "description": "Template used to create merge commit message in merge requests.", 

1154 }, 

1155 "squash_commit_template": { 

1156 "type": "string", 

1157 "description": "Template used to create squash commit message in merge requests.", 

1158 }, 

1159 "merge_requests_template": { 

1160 "type": "string", 

1161 "description": "Default description for merge requests.", 

1162 }, 

1163 "approvals_before_merge": { 

1164 "type": "integer", 

1165 "description": "How many approvers should approve merge request by default", 

1166 "multipleOf": 1, 

1167 "minimum": 0, 

1168 }, 

1169 # From https://docs.gitlab.com/ee/api/projects.html#edit-project 

1170 "public_jobs": { 

1171 "type": "boolean", 

1172 "description": "If true, jobs can be viewed by non-project members.", 

1173 }, 

1174 "auto_cancel_pending_pipelines": { 

1175 "type": "string", 

1176 "description": "Auto-cancel pending pipelines", 

1177 "enum": ["enabled", "disabled"], 

1178 }, 

1179 "ci_forward_deployment_enabled": { 

1180 "type": "boolean", 

1181 "description": "Enable or disable prevent outdated deployment jobs.", 

1182 }, 

1183 "ci_forward_deployment_rollback_allowed": { 

1184 "type": "boolean", 

1185 "description": "Enable or disable allow job retries for rollback deployments.", 

1186 }, 

1187 "ci_separated_caches": { 

1188 "type": "boolean", 

1189 "description": "Set whether or not caches should be separated by branch protection status.", 

1190 }, 

1191 "ci_restrict_pipeline_cancellation_role": { 

1192 "type": "string", 

1193 "description": "Set the role required to cancel a pipeline or job.", 

1194 "enum": ["developer", "maintainer", "no_one"], 

1195 }, 

1196 "ci_config_path": { 

1197 "type": "string", 

1198 "description": "The path to CI config file", 

1199 "_example": "debian/salsa-ci.yml", 

1200 }, 

1201 "build_git_strategy": { 

1202 "type": "string", 

1203 "description": "The Git strategy", 

1204 "enum": ["fetch", "clone"], 

1205 }, 

1206 "ci_default_git_depth": { 

1207 "type": "integer", 

1208 "description": "Default number of revisions for shallow cloning", 

1209 }, 

1210 "build_timeout": { 

1211 "type": "integer", 

1212 "description": "The maximum amount of time in minutes that a job is able run (in seconds)", 

1213 }, 

1214 "ci_allow_fork_pipelines_to_run_in_parent_project": { 

1215 "type": "boolean", 

1216 "description": ( 

1217 "Enable or disable running pipelines in the parent project for merge requests from forks." 

1218 ), 

1219 }, 

1220 "auto_devops_enabled": { 

1221 "type": "boolean", 

1222 "description": "Enable Auto DevOps for this project", 

1223 }, 

1224 "auto_devops_deploy_strategy": { 

1225 "type": "string", 

1226 "description": "Auto Deploy strategy", 

1227 "enum": ["continuous", "manual", "timed_incremental"], 

1228 }, 

1229 "allow_pipeline_trigger_approve_deployment": { 

1230 "type": "boolean", 

1231 "description": "Set whether or not a pipeline triggerer is allowed to approve deployments.", 

1232 }, 

1233 "shared_runners_enabled": { 

1234 "type": "boolean", 

1235 "description": "Enable shared runners for this project.", 

1236 }, 

1237 "group_runners_enabled": { 

1238 "type": "boolean", 

1239 "description": "Enable group runners for this project.", 

1240 }, 

1241 "keep_latest_artifact": { 

1242 "type": "boolean", 

1243 "description": "Disable or enable the ability to keep the latest artifact for this project.", 

1244 }, 

1245 # From https://docs.gitlab.com/ee/api/project_level_variables.html#create-variable 

1246 "variables": { 

1247 "type": "array", 

1248 "description": "The list of project's variables", 

1249 "items": { 

1250 "type": "object", 

1251 "properties": { 

1252 "key": { 

1253 "type": "string", 

1254 "description": "The key of a variable.", 

1255 "pattern": "[a-zA-Z0-9_]+", 

1256 "maxLength": 255, 

1257 }, 

1258 "value": { 

1259 "type": "string", 

1260 "description": "The value of a variable.", 

1261 }, 

1262 "description": { 

1263 "type": "string", 

1264 "description": "The description of the variable.", 

1265 }, 

1266 "variable_type": { 

1267 "type": "string", 

1268 "description": "The type of a variable. Available types are: env_var (default) and file.", 

1269 "enum": ["env_var", "file"], 

1270 }, 

1271 "protected": { 

1272 "type": "boolean", 

1273 "description": "Whether the variable is protected.", 

1274 }, 

1275 "masked": { 

1276 "type": "boolean", 

1277 "description": "Whether the variable is masked.", 

1278 }, 

1279 "raw": { 

1280 "type": "boolean", 

1281 "description": "Whether the variable is treated as a raw string.", 

1282 }, 

1283 "environment_scope": { # Premium+/Silver+ 

1284 "type": "string", 

1285 "description": "The environment_scope of the variable.", 

1286 }, 

1287 }, 

1288 "required": ["key"], 

1289 "additionalProperties": False, 

1290 }, 

1291 "uniqueItems": True, 

1292 "_example": ( 

1293 "\n" 

1294 " - key: DAST_DISABLED\n" 

1295 " value: '1'\n" 

1296 " description: Disabled SAST\n" 

1297 " masked: false\n" 

1298 " protected: false\n" 

1299 " raw: false # Expand variables\n" 

1300 " environment_scope: '*'\n" 

1301 " variable_type: env_var\n" 

1302 ), 

1303 }, 

1304 "unknown_variables": { # GitLabracadabra 

1305 "type": "string", 

1306 "description": "What to do with unknown variables (`warn` by default).", 

1307 "enum": ["warn", "delete", "remove", "ignore", "skip"], 

1308 }, 

1309 # From https://docs.gitlab.com/ee/api/projects.html#edit-project 

1310 # container_expiration_policy_attributes 

1311 "container_expiration_policy": { 

1312 "type": "object", 

1313 "description": "Update the image cleanup policy for this project", 

1314 "properties": { 

1315 "enabled": { 

1316 "type": "boolean", 

1317 }, 

1318 "cadence": { 

1319 "type": "string", 

1320 }, 

1321 "keep_n": { 

1322 "type": "integer", 

1323 }, 

1324 "name_regex_keep": { 

1325 "type": "string", 

1326 }, 

1327 "older_than": { 

1328 "type": "string", 

1329 }, 

1330 "name_regex_delete": { 

1331 "type": "string", 

1332 }, 

1333 }, 

1334 "required": ["enabled"], 

1335 "additionalProperties": False, 

1336 "_example": ( 

1337 "\n" 

1338 " enabled: true\n" 

1339 " cadence: 7d # 1d, 7d, 14d, 1month, 3month\n" 

1340 " keep_n: 10 # 1, 5, 10, 25, 50, 100\n" 

1341 " name_regex_keep: '.*main|.*release|release-.*|main-.*'\n" 

1342 " older_than: 90d # 7d, 14d, 30d, 90d\n" 

1343 " name_regex_delete: '.*'\n" 

1344 ), 

1345 }, 

1346 # GitLabracadabra 

1347 "mirrors": { 

1348 "type": "array", 

1349 "description": "The list of project's mirrors", 

1350 "items": { 

1351 "type": "object", 

1352 "properties": { 

1353 "enabled": { 

1354 "type": "boolean", 

1355 }, 

1356 "url": { 

1357 "type": "string", 

1358 "description": "Repository URL", 

1359 }, 

1360 "auth_id": { 

1361 "type": "string", 

1362 "description": "Section from .python-gitlab.cfg for authentication", 

1363 }, 

1364 "direction": { 

1365 "type": "string", 

1366 "description": "Mirror direction", 

1367 "enum": ["pull", "push"], 

1368 }, 

1369 "skip_ci": { 

1370 "type": "boolean", 

1371 "description": "Skip CI during push", 

1372 }, 

1373 "push_options": { 

1374 "type": "array", 

1375 "description": "Default push options", 

1376 "items": { 

1377 "type": "string", 

1378 }, 

1379 }, 

1380 "branches": { 

1381 "type": "array", 

1382 "description": "The branches mapping", 

1383 "items": { 

1384 "type": "object", 

1385 "properties": { 

1386 "from": { 

1387 "type": "string", 

1388 "description": "Source name or regular expression", 

1389 }, 

1390 "to": { 

1391 "type": "string", 

1392 "description": "Destination name or regular expression template", 

1393 }, 

1394 "push_options": { 

1395 "type": "array", 

1396 "description": "Push options", 

1397 "items": { 

1398 "type": "string", 

1399 }, 

1400 }, 

1401 }, 

1402 "required": ["from"], 

1403 "additionalProperties": False, 

1404 }, 

1405 "uniqueItems": True, 

1406 }, 

1407 "tags": { 

1408 "type": "array", 

1409 "description": "The tags mapping", 

1410 "items": { 

1411 "type": "object", 

1412 "properties": { 

1413 "from": { 

1414 "type": "string", 

1415 "description": "Source name or regular expression", 

1416 }, 

1417 "to": { 

1418 "type": "string", 

1419 "description": "Destination name or regular expression template", 

1420 }, 

1421 "push_options": { 

1422 "type": "array", 

1423 "description": "Push options", 

1424 "items": { 

1425 "type": "string", 

1426 }, 

1427 }, 

1428 }, 

1429 "required": ["from"], 

1430 "additionalProperties": False, 

1431 }, 

1432 "uniqueItems": True, 

1433 }, 

1434 }, 

1435 "required": ["url"], 

1436 "additionalProperties": False, 

1437 }, 

1438 "uniqueItems": True, 

1439 "_example": ( 

1440 "\n" 

1441 " - url: https://gitlab.com/gitlabracadabra/gitlabracadabra.git\n" 

1442 " # auth_id: gitlab # Section from .python-gitlab.cfg for authentication\n" 

1443 " direction: pull # one of pull, push ; only first pull mirror is processed\n" 

1444 " push_options: [ci.skip]\n" 

1445 " branches: # if you omit this parameter, all branches are mirrored\n" 

1446 " - from: '/wip-.*/'\n" 

1447 " to: '' # This will skip those branches\n" 

1448 " - from: main\n" 

1449 " # to: main # implicitly equal to source branch\n" 

1450 " # push_options: [] # inherited by default\n" 

1451 " # Using regexps\n" 

1452 " - from: '/(.*)/'\n" 

1453 " to: 'upstream/\\1'\n" 

1454 " tags: # if you omit this parameter, all tags are mirrored\n" 

1455 " - from: '/v(.*)/i'\n" 

1456 " to: 'upstream-\\1'\n" 

1457 " # push_options: [] # inherited by default\n" 

1458 " builds_access_level: disabled # If you want to prevent triggering pipelines on push" 

1459 ), 

1460 "_doc_link": "mirrors.md", 

1461 "x-gitlabracadabra-order": 10, 

1462 }, 

1463 "package_mirrors": { 

1464 "type": "array", 

1465 "description": "Package image mirrors", 

1466 "items": { 

1467 "oneOf": [ 

1468 { 

1469 "type": "object", 

1470 "description": "Github source", 

1471 "properties": { 

1472 "enabled": { 

1473 "type": "boolean", 

1474 }, 

1475 "github": { 

1476 "type": "object", 

1477 "properties": { 

1478 "full_name": { 

1479 "type": "string", 

1480 }, 

1481 "package_name": { 

1482 "type": "string", 

1483 }, 

1484 "tags": { 

1485 "type": "array", 

1486 "items": { 

1487 "type": "string", 

1488 }, 

1489 }, 

1490 "semver": { 

1491 "type": "string", 

1492 }, 

1493 "latest_release": { 

1494 "type": "boolean", 

1495 }, 

1496 "tarball": { 

1497 "type": "boolean", 

1498 }, 

1499 "zipball": { 

1500 "type": "boolean", 

1501 }, 

1502 "assets": { 

1503 "type": "array", 

1504 "items": { 

1505 "type": "string", 

1506 }, 

1507 }, 

1508 }, 

1509 "required": ["full_name"], 

1510 "additionalProperties": False, 

1511 }, 

1512 }, 

1513 "additionalProperties": False, 

1514 }, 

1515 { 

1516 "type": "object", 

1517 "description": "Helm source", 

1518 "properties": { 

1519 "enabled": { 

1520 "type": "boolean", 

1521 }, 

1522 "helm": { 

1523 "type": "object", 

1524 "properties": { 

1525 "repo_url": { 

1526 "type": "string", 

1527 }, 

1528 "package_name": { 

1529 "type": "string", 

1530 }, 

1531 "versions": { 

1532 "type": "array", 

1533 "items": { 

1534 "type": "string", 

1535 }, 

1536 }, 

1537 "semver": { 

1538 "type": "string", 

1539 }, 

1540 "limit": { 

1541 "type": "integer", 

1542 }, 

1543 "channel": { 

1544 "type": "string", 

1545 }, 

1546 }, 

1547 "required": ["repo_url", "package_name"], 

1548 "additionalProperties": False, 

1549 }, 

1550 }, 

1551 "additionalProperties": False, 

1552 }, 

1553 { 

1554 "type": "object", 

1555 "description": "PULP_MANIFEST repository", 

1556 "properties": { 

1557 "enabled": { 

1558 "type": "boolean", 

1559 }, 

1560 "pulp_manifest": { 

1561 "type": "object", 

1562 "properties": { 

1563 "url": { 

1564 "type": "string", 

1565 }, 

1566 "package_name": { 

1567 "type": "string", 

1568 }, 

1569 "package_version": { 

1570 "type": "string", 

1571 }, 

1572 }, 

1573 "required": ["url", "package_name"], 

1574 "additionalProperties": False, 

1575 }, 

1576 }, 

1577 "additionalProperties": False, 

1578 }, 

1579 { 

1580 "type": "object", 

1581 "description": "PyPI source", 

1582 "properties": { 

1583 "enabled": { 

1584 "type": "boolean", 

1585 }, 

1586 "pypi": { 

1587 "type": "object", 

1588 "properties": { 

1589 "index_url": { 

1590 "type": "string", 

1591 }, 

1592 "requirements": { 

1593 "oneOf": [ 

1594 { 

1595 "type": "string", 

1596 }, 

1597 { 

1598 "type": "array", 

1599 "items": { 

1600 "type": "string", 

1601 }, 

1602 }, 

1603 ], 

1604 }, 

1605 }, 

1606 "required": ["requirements"], 

1607 "additionalProperties": False, 

1608 }, 

1609 }, 

1610 "additionalProperties": False, 

1611 }, 

1612 { 

1613 "type": "object", 

1614 "description": "Raw source", 

1615 "properties": { 

1616 "enabled": { 

1617 "type": "boolean", 

1618 }, 

1619 "raw": { 

1620 "type": "object", 

1621 "properties": { 

1622 "default_url": { 

1623 "type": "string", 

1624 }, 

1625 "default_package_name": { 

1626 "type": "string", 

1627 }, 

1628 "default_package_version": { 

1629 "type": "string", 

1630 }, 

1631 "package_files": { 

1632 "type": "array", 

1633 "items": { 

1634 "type": "object", 

1635 "properties": { 

1636 "url": { 

1637 "type": "string", 

1638 }, 

1639 "package_name": { 

1640 "type": "string", 

1641 }, 

1642 "package_version": { 

1643 "type": "string", 

1644 }, 

1645 "file_name": { 

1646 "type": "string", 

1647 }, 

1648 }, 

1649 "additionalProperties": False, 

1650 }, 

1651 }, 

1652 }, 

1653 "required": ["default_url"], 

1654 "additionalProperties": False, 

1655 }, 

1656 }, 

1657 "additionalProperties": False, 

1658 }, 

1659 ], 

1660 }, 

1661 "_example": ( 

1662 "\n" 

1663 " - raw:\n" 

1664 " default_url: https://download.docker.com/linux/debian/gpg\n" 

1665 " default_package_name: docker\n" 

1666 " default_package_version: '0'\n" 

1667 ), 

1668 "_doc_link": "package_mirrors.md", 

1669 "x-gitlabracadabra-order": 10, 

1670 }, 

1671 "image_mirrors": { 

1672 "type": "array", 

1673 "description": "Container image mirrors", 

1674 "items": { 

1675 "type": "object", 

1676 "properties": { 

1677 "enabled": { 

1678 "type": "boolean", 

1679 }, 

1680 "from": { 

1681 "oneOf": [ 

1682 { 

1683 "type": "string", 

1684 "description": "The source image", 

1685 "pattern": ".+", 

1686 }, 

1687 { 

1688 "type": "object", 

1689 "properties": { 

1690 "base": { 

1691 "type": "string", 

1692 }, 

1693 "repositories": { 

1694 "type": "array", 

1695 "items": { 

1696 "type": "string", 

1697 }, 

1698 }, 

1699 "tags": { 

1700 "type": "array", 

1701 "items": { 

1702 "type": "string", 

1703 }, 

1704 }, 

1705 }, 

1706 "required": ["repositories"], 

1707 "additionalProperties": False, 

1708 }, 

1709 ], 

1710 }, 

1711 "to": { 

1712 "oneOf": [ 

1713 { 

1714 "type": "string", 

1715 "description": "The destination image", 

1716 }, 

1717 { 

1718 "type": "object", 

1719 "properties": { 

1720 "base": { 

1721 "type": "string", 

1722 }, 

1723 "repository": { 

1724 "type": "string", 

1725 }, 

1726 "tag": { 

1727 "type": "string", 

1728 }, 

1729 }, 

1730 "additionalProperties": False, 

1731 }, 

1732 ], 

1733 }, 

1734 "semver": { 

1735 "type": "string", 

1736 "description": "Version specification as an NPM range", 

1737 }, 

1738 }, 

1739 "required": ["from"], 

1740 "additionalProperties": False, 

1741 }, 

1742 "_example": ( 

1743 "\n" 

1744 " # Mirror debian:trixie\n" 

1745 " # ... to registry.example.org/mygroup/myproject/library/debian:trixie:\n" 

1746 " - from: 'debian:trixie'\n" 

1747 " # Overriding destination:\n" 

1748 " - from: 'quay.org/coreos/etcd:v3.4.1'\n" 

1749 " to: 'etcd:v3.4.1' # Default would be coreos/etcd:v3.4.1\n" 

1750 ), 

1751 "_doc_link": "image_mirrors.md", 

1752 "x-gitlabracadabra-order": 10, 

1753 }, 

1754 # From https://docs.gitlab.com/ee/api/projects.html#edit-project 

1755 # Deprecated 

1756 "build_coverage_regex": { 

1757 "type": "string", 

1758 "description": "(Removed) Test coverage parsing.", 

1759 }, 

1760 "container_registry_enabled": { 

1761 "type": "boolean", 

1762 "description": ( 

1763 "(Deprecated) Enable container registry for this project. " 

1764 "Use container_registry_access_level instead." 

1765 ), 

1766 }, 

1767 "emails_disabled": { 

1768 "type": "boolean", 

1769 "description": "(Deprecated) Disable email notifications. Use emails_enabled instead.", 

1770 }, 

1771 "issues_enabled": { 

1772 "type": "boolean", 

1773 "description": "(Deprecated) Enable issues for this project. Use issues_access_level instead.", 

1774 }, 

1775 "jobs_enabled": { 

1776 "type": "boolean", 

1777 "description": "(Deprecated) Enable jobs for this project. Use builds_access_level instead.", 

1778 }, 

1779 "merge_requests_enabled": { 

1780 "type": "boolean", 

1781 "description": ( 

1782 "(Deprecated) Enable merge requests for this project. Use merge_requests_access_level instead." 

1783 ), 

1784 }, 

1785 "public_builds": { 

1786 "type": "boolean", 

1787 "description": ( 

1788 "(Deprecated) If true, jobs can be viewed by non-project members. Use public_jobs instead." 

1789 ), 

1790 }, 

1791 "snippets_enabled": { 

1792 "type": "boolean", 

1793 "description": "(Deprecated) Enable snippets for this project. Use snippets_access_level instead.", 

1794 }, 

1795 "tag_list": { 

1796 "type": "array", 

1797 "description": ( 

1798 "(Deprecated in GitLab 14.0) The list of tags for a project; put array of tags, " 

1799 "that should be finally assigned to a project. Use topics instead." 

1800 ), 

1801 "items": { 

1802 "type": "string", 

1803 }, 

1804 "uniqueItems": True, 

1805 "_example": "[GitLab, API, YAML]", 

1806 }, 

1807 "wiki_enabled": { 

1808 "type": "boolean", 

1809 "description": "(Deprecated) Enable wiki for this project. Use wiki_access_level instead.", 

1810 }, 

1811 # Below are undocumented settings 

1812 "repository_storage": { 

1813 "type": "string", 

1814 "description": "Which storage shard the repository is on. Available only to admins", 

1815 }, 

1816 "external_authorization_classification_label": { 

1817 "type": "string", 

1818 "description": "The classification label for the project", 

1819 }, 

1820 }, 

1821 "additionalProperties": False, 

1822 } 

1823 

1824 IGNORED_PARAMS: ClassVar[list[str]] = [ 

1825 "initialize_with_readme", 

1826 "repository_object_format", 

1827 "unknown_boards", 

1828 "unknown_board_lists", 

1829 "unknown_groups", 

1830 "unknown_labels", 

1831 "unknown_members", 

1832 "unknown_milestones", 

1833 "unknown_pipeline_schedules", 

1834 "unknown_pipeline_schedule_variables", 

1835 "unknown_protected_branches", 

1836 "unknown_protected_tags", 

1837 "unknown_variables", 

1838 "unknown_webhooks", 

1839 ] 

1840 

1841 CREATE_KEY = "name" 

1842 CREATE_PARAMS: ClassVar[list[str]] = ["initialize_with_readme", "repository_object_format"] 

1843 

1844 def _get_current_branches(self): 

1845 if not hasattr(self, "_current_branches"): 

1846 try: 

1847 self._current_branches = [branch.name for branch in self._obj.branches.list(all=True)] 

1848 except GitlabListError as err: 

1849 if err.response_code != HTTPStatus.FORBIDDEN: # repository_enabled=false? 

1850 pass 

1851 self._current_branches = None 

1852 return self._current_branches 

1853 

1854 """"_process_archived() 

1855 

1856 Process the archived param. 

1857 """ 

1858 

1859 def _process_archived(self, param_name, param_value, *, dry_run=False, skip_save=False): 

1860 assert param_name == "archived" # noqa: S101 

1861 assert not skip_save # noqa: S101 

1862 

1863 current_value = getattr(self._obj, param_name) 

1864 if current_value != param_value: 1864 ↛ exitline 1864 didn't return from function '_process_archived' because the condition on line 1864 was always true

1865 if dry_run: 1865 ↛ 1866line 1865 didn't jump to line 1866 because the condition on line 1865 was never true

1866 logger.info( 

1867 "[%s] NOT Changing param %s: %s -> %s (dry-run)", self._name, param_name, current_value, param_value 

1868 ) 

1869 setattr(self._obj, param_name, param_value) 

1870 else: 

1871 logger.info("[%s] Changing param %s: %s -> %s", self._name, param_name, current_value, param_value) 

1872 if param_value: 

1873 self._obj.archive() 

1874 else: 

1875 self._obj.unarchive() 

1876 

1877 """"_process_branches() 

1878 

1879 Process the branches param. 

1880 """ 

1881 

1882 def _process_branches(self, param_name, param_value, *, dry_run=False, skip_save=False): 

1883 assert param_name == "branches" # noqa: S101 

1884 assert not skip_save # noqa: S101 

1885 if "default_branch" in self._content and self._content["default_branch"] in self._get_current_branches(): 1885 ↛ 1887line 1885 didn't jump to line 1887 because the condition on line 1885 was never true

1886 # Create from target default branch if it exists 

1887 ref = self._content["default_branch"] 

1888 elif self._obj.default_branch in self._get_current_branches(): 1888 ↛ 1892line 1888 didn't jump to line 1892 because the condition on line 1888 was always true

1889 # Create from current default branch otherwise 

1890 ref = self._obj.default_branch 

1891 else: 

1892 ref = None 

1893 for branch_name in param_value: 

1894 if branch_name not in self._get_current_branches(): 1894 ↛ 1909line 1894 didn't jump to line 1909 because the condition on line 1894 was always true

1895 if ref is None: 1895 ↛ 1896line 1895 didn't jump to line 1896 because the condition on line 1895 was never true

1896 logger.info("[%s] NOT Creating branch: %s (no reference)", self._name, branch_name) 

1897 elif dry_run: 1897 ↛ 1898line 1897 didn't jump to line 1898 because the condition on line 1897 was never true

1898 logger.info("[%s] NOT Creating branch: %s (dry-run)", self._name, branch_name) 

1899 self._current_branches.append(branch_name) 

1900 else: 

1901 logger.info("[%s] Creating branch: %s", self._name, branch_name) 

1902 self._obj.branches.create( 

1903 { 

1904 "branch": branch_name, 

1905 "ref": ref, 

1906 } 

1907 ) 

1908 self._current_branches.append(branch_name) 

1909 if branch_name in self._get_current_branches(): 1909 ↛ 1893line 1909 didn't jump to line 1893 because the condition on line 1909 was always true

1910 # Next branch will be created from this ref 

1911 ref = branch_name 

1912 

1913 """"_process_protected_tags() 

1914 

1915 Process the protected_tags param. 

1916 """ 

1917 

1918 def _process_protected_tags(self, param_name, param_value, *, dry_run=False, skip_save=False): 

1919 assert param_name == "protected_tags" # noqa: S101 

1920 assert not skip_save # noqa: S101 

1921 unknown_protected_tags = self._content.get("unknown_protected_tags", "warn") 

1922 try: 

1923 current_protected_tags = dict( 

1924 [[protected_tag.name, protected_tag] for protected_tag in self._obj.protectedtags.list(all=True)] 

1925 ) 

1926 except AttributeError: 

1927 logger.error( 

1928 "[%s] Unable to manage protected tags: %s", self._name, "protected tags requires python-gitlab >= 1.7.0" 

1929 ) 

1930 return 

1931 # We first check for already protected tags 

1932 for protected_name, target_config in sorted(param_value.items()): 

1933 target_config = { 

1934 "name": protected_name, 

1935 "create_access_level": access_level_value(target_config), 

1936 } 

1937 if protected_name in current_protected_tags: 

1938 current_protected_tag = current_protected_tags[protected_name] 

1939 current_config = { 

1940 "name": protected_name, 

1941 "create_access_level": current_protected_tag.create_access_levels[0]["access_level"], 

1942 } 

1943 else: 

1944 current_config = {} 

1945 if current_config != target_config: 1945 ↛ 1932line 1945 didn't jump to line 1932 because the condition on line 1945 was always true

1946 if dry_run: 1946 ↛ 1947line 1946 didn't jump to line 1947 because the condition on line 1946 was never true

1947 logger.info( 

1948 "[%s] NOT Changing protected tag %s access level: %s -> %s (dry-run)", 

1949 self._name, 

1950 protected_name, 

1951 current_config, 

1952 target_config, 

1953 ) 

1954 else: 

1955 logger.info( 

1956 "[%s] Changing protected tag %s access level: %s -> %s", 

1957 self._name, 

1958 protected_name, 

1959 current_config, 

1960 target_config, 

1961 ) 

1962 if "name" in current_config: 

1963 self._obj.protectedtags.delete(protected_name) 

1964 self._obj.protectedtags.create(target_config) 

1965 # Remaining protected tags 

1966 if unknown_protected_tags not in ["ignore", "skip"]: 1966 ↛ exitline 1966 didn't return from function '_process_protected_tags' because the condition on line 1966 was always true

1967 current_protected_tags = sorted( 

1968 protected_tag.name for protected_tag in self._obj.protectedtags.list(all=True) 

1969 ) 

1970 for protected_name in current_protected_tags: 

1971 if protected_name not in param_value: 

1972 if unknown_protected_tags in ["delete", "remove"]: 1972 ↛ 1981line 1972 didn't jump to line 1981 because the condition on line 1972 was always true

1973 if dry_run: 1973 ↛ 1974line 1973 didn't jump to line 1974 because the condition on line 1973 was never true

1974 logger.info( 

1975 "[%s] NOT Deleting unknown protected tag: %s (dry-run)", self._name, protected_name 

1976 ) 

1977 else: 

1978 logger.info("[%s] Deleting unknown protected tag: %s", self._name, protected_name) 

1979 self._obj.protectedtags.delete(protected_name) 

1980 else: 

1981 logger.warning( 

1982 "[%s] NOT Deleting unknown protected tag: %s (unknown_protected_tags=%s)", 

1983 self._name, 

1984 protected_name, 

1985 unknown_protected_tags, 

1986 ) 

1987 

1988 """"_process_container_expiration_policy() 

1989 

1990 Process the container_expiration_policy param. 

1991 """ 

1992 

1993 def _process_container_expiration_policy(self, param_name, param_value, *, dry_run=False, skip_save=False): 

1994 assert param_name == "container_expiration_policy" # noqa: S101 

1995 assert not skip_save # noqa: S101 

1996 

1997 current_value = getattr(self._obj, param_name) 

1998 

1999 for k, v in sorted(param_value.items()): 

2000 current_v = current_value.get(k, None) 

2001 if v != current_v: 

2002 if dry_run: 2002 ↛ 2003line 2002 didn't jump to line 2003 because the condition on line 2002 was never true

2003 logger.info( 

2004 "[%s] NOT Changing container expiration policy %s: %s -> %s (dry-run)", 

2005 self._name, 

2006 k, 

2007 current_v, 

2008 v, 

2009 ) 

2010 else: 

2011 logger.info("[%s] Changing container expiration policy %s: %s -> %s", self._name, k, current_v, v) 

2012 self._obj.container_expiration_policy_attributes = {k: v} 

2013 self._obj.save()