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

17 import logging

18 from http import HTTPStatus

19 from typing import ClassVar

20

21 from gitlab.exceptions import GitlabListError

22

23 from gitlabracadabra.gitlab.access_levels import access_level_value

24 from gitlabracadabra.mixins.boards import BoardsMixin

25 from gitlabracadabra.mixins.groups import GroupsMixin

26 from gitlabracadabra.mixins.image_mirrors import ImageMirrorsMixin

27 from gitlabracadabra.mixins.labels import LabelsMixin

28 from gitlabracadabra.mixins.members import MembersMixin

29 from gitlabracadabra.mixins.milestones import MilestonesMixin

30 from gitlabracadabra.mixins.mirrors import MirrorsMixin

31 from gitlabracadabra.mixins.package_mirrors import PackageMirrorsMixin

32 from gitlabracadabra.mixins.pipeline_schedules import PipelineSchedulesMixin

33 from gitlabracadabra.mixins.protected_branches import ProtectedBranchesMixin

34 from gitlabracadabra.mixins.rename_branches import RenameBranchesMixin

35 from gitlabracadabra.mixins.variables import VariablesMixin

36 from gitlabracadabra.mixins.webhooks import WebhooksMixin

37 from gitlabracadabra.objects.object import GitLabracadabraObject

38

39 logger = logging.getLogger(__name__)

40

41

42 class 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

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

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

213 # 'auto_rollback_enabled', # FIXME: GitLab

214 # '### Deploy freezes', # FIXME

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

216 # '### Secure files', # FIXME

217 # '### Pipeline subscriptions', # FIXME

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"

279 " foo: developer\n"

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

281 ),

282 },

283 "unknown_members": { # GitLabracadabra

284 "type": "string",

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

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

287 },

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

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

290 # FIXME: expires_at not supported

291 "groups": {

292 "type": "object",

293 "description": "Groups",

294 "additionalProperties": {

295 "type": "string",

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

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

298 },

299 "_example": (

300 "\n"

301 " group/foo: guest\n"

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

303 ),

304 },

305 "unknown_groups": { # GitLabracadabra

306 "type": "string",

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

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

309 },

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

311 "labels": {

312 "type": "array",

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

314 "items": {

315 "type": "object",

316 "properties": {

317 "name": {

318 "type": "string",

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

320 },

321 "color": {

322 "type": "string",

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

324 },

325 "description": {

326 "type": "string",

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

328 },

329 "priority": {

330 "type": "integer",

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

332 },

333 },

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

335 "additionalProperties": False,

336 },

337 "uniqueItems": True,

338 "_example": (

339 "\n"

340 " - name: critical\n"

341 " priority: 0\n"

342 " - name: bug\n"

343 " priority: 1\n"

344 " - name: confirmed\n"

345 " priority: 2\n"

346 ),

347 },

348 "unknown_labels": { # GitLabracadabra

349 "type": "string",

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

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

352 },

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

354 "boards": {

355 "type": "array",

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

357 "items": {

358 "type": "object",

359 "properties": {

360 "name": {

361 "type": "string",

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

363 },

364 "old_name": {

365 "type": "string",

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

367 },

368 "hide_backlog_list": {

369 "type": "boolean",

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

371 },

372 "hide_closed_list": {

373 "type": "boolean",

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

375 },

376 "lists": {

377 "type": "array",

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

379 "items": {

380 "type": "object",

381 "properties": {

382 "label": {

383 "type": "string",

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

385 },

386 },

387 },

388 },

389 "unknown_lists": { # GitLabracadabra

390 "type": "string",

391 "description": (

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

393 ),

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

395 },

396 },

397 "required": ["name"],

398 "additionalProperties": False,

399 },

400 "uniqueItems": True,

401 "_example": (

402 "\n"

403 " - name: My Board\n"

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

405 " hide_backlog_list: false\n"

406 " hide_closed_list: false\n"

407 " lists:\n"

408 " - label: TODO\n"

409 " - label: WIP\n"

410 ),

411 },

412 "unknown_boards": { # GitLabracadabra

413 "type": "string",

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

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

416 },

417 "unknown_board_lists": { # GitLabracadabra

418 "type": "string",

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

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

421 },

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

423 "milestones": {

424 "type": "array",

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

426 "items": {

427 "type": "object",

428 "properties": {

429 "title": {

430 "type": "string",

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

432 },

433 "description": {

434 "type": "string",

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

436 },

437 "due_date": {

438 "type": "string",

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

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

441 },

442 "start_date": {

443 "type": "string",

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

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

446 },

447 "state": {

448 "type": "string",

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

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

451 },

452 },

453 "required": ["title"],

454 "additionalProperties": False,

455 },

456 "uniqueItems": True,

457 "_example": (

458 "\n"

459 " - title: '1.0'\n"

460 " description: Version 1.0\n"

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

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

463 " state: active # or closed\n"

464 ),

465 },

466 "unknown_milestones": { # GitLabracadabra

467 "type": "string",

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

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

470 },

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

472 "branches": {

473 "type": "array",

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

475 "items": {

476 "type": "string",

477 },

478 "uniqueItems": True,

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

480 },

481 "rename_branches": {

482 "type": "array",

483 "description": "Rename branches of a project. "

484 "Rename pairs (old_name: new_name) are processed in order",

485 "items": {

486 "type": "object",

487 "additionalProperties": {

488 "type": "string",

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

490 },

491 "minProperties": 1,

492 "maxProperties": 1,

493 },

494 "uniqueItems": True,

495 "_example": (

496 "\n"

497 " - old_name: new_name\n"

498 " # To Rename consecutive branches:\n"

499 " - branch2: branch3\n"

500 " - branch1: branch2"

501 ),

502 },

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

504 "pipeline_schedules": {

505 "type": "array",

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

507 "items": {

508 "type": "object",

509 "properties": {

510 "description": {

511 "type": "string",

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

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

514 },

515 "ref": {

516 "type": "string",

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

518 },

519 "cron": {

520 "type": "string",

521 "description": (

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

523 ),

524 },

525 "cron_timezone": {

526 "type": "string",

527 "description": (

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

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

530 ),

531 },

532 "active": {

533 "type": "boolean",

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

535 },

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

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

538 "variables": {

539 "type": "array",

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

541 "items": {

542 "type": "object",

543 "properties": {

544 "key": {

545 "type": "string",

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

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

548 },

549 "value": {

550 "type": "string",

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

552 },

553 "variable_type": {

554 "type": "string",

555 "description": (

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

557 ),

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

559 },

560 },

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

562 "additionalProperties": False,

563 },

564 "uniqueItems": True,

565 },

566 "unknown_variables": { # GitLabracadabra

567 "type": "string",

568 "description": (

569 "What to do with unknown pipeline schedule variables "

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

571 ),

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

573 },

574 },

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

576 "additionalProperties": False,

577 },

578 "uniqueItems": True,

579 "_example": (

580 "\n"

581 " - description: Build packages\n"

582 " ref: main\n"

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

584 " # cron_timezone: UTC\n"

585 " # active: true\n"

586 " variables:\n"

587 " - key: MY_VAR\n"

588 " value: my value\n"

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

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

591 ),

592 },

593 "unknown_pipeline_schedules": { # GitLabracadabra

594 "type": "string",

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

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

597 },

598 "unknown_pipeline_schedule_variables": { # GitLabracadabra

599 "type": "string",

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

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

602 },

603 "name": {

604 "type": "string",

605 "description": "Project name",

606 },

607 # 'path': {

608 # 'type': 'string',

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

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

611 # },

612 "description": {

613 "type": "string",

614 "description": "Project description",

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

616 },

617 "topics": {

618 "type": "array",

619 "description": "Topics",

620 "items": {

621 "type": "string",

622 },

623 "uniqueItems": True,

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

625 },

626 # 'avatar': {

627 # 'type': 'string',

628 # 'description': 'Project avatar',

629 # },

630 "visibility": {

631 "type": "string",

632 "description": "Project visibility",

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

634 },

635 "request_access_enabled": {

636 "type": "boolean",

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

638 },

639 "issues_access_level": {

640 "type": "string",

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

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

643 },

644 "repository_access_level": {

645 "type": "string",

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

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

648 },

649 "merge_requests_access_level": {

650 "type": "string",

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

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

653 },

654 "forking_access_level": {

655 "type": "string",

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

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

658 },

659 "lfs_enabled": {

660 "type": "boolean",

661 "description": "Enable LFS",

662 },

663 "builds_access_level": {

664 "type": "string",

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

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

667 },

668 "container_registry_access_level": {

669 "type": "string",

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

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

672 },

673 "analytics_access_level": {

674 "type": "string",

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

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

677 },

678 "requirements_access_level": {

679 "type": "string",

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

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

682 },

683 "security_and_compliance_access_level": {

684 "type": "string",

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

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

687 },

688 "wiki_access_level": {

689 "type": "string",

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

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

692 },

693 "snippets_access_level": {

694 "type": "string",

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

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

697 },

698 "packages_enabled": {

699 "type": "boolean",

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

701 },

702 "model_experiments_access_level": {

703 "type": "string",

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

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

706 },

707 "model_registry_access_level": {

708 "type": "string",

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

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

711 },

712 "pages_access_level": {

713 "type": "string",

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

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

716 },

717 "monitor_access_level": {

718 "type": "string",

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

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

721 },

722 "environments_access_level": {

723 "type": "string",

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

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

726 },

727 "feature_flags_access_level": {

728 "type": "string",

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

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

731 },

732 "infrastructure_access_level": {

733 "type": "string",

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

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

736 },

737 "releases_access_level": {

738 "type": "string",

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

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

741 },

742 "emails_enabled": {

743 "type": "boolean",

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

745 },

746 "issues_template": {

747 "type": "string",

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

749 },

750 "service_desk_enabled": {

751 "type": "boolean",

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

753 },

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

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

756 "archived": {

757 "type": "boolean",

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

759 },

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

761 "webhooks": {

762 "type": "array",

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

764 "items": {

765 "type": "object",

766 "properties": {

767 "url": {

768 "type": "string",

769 "description": "The hook URL",

770 },

771 "push_events": {

772 "type": "boolean",

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

774 },

775 "push_events_branch_filter": {

776 "type": "string",

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

778 },

779 "issues_events": {

780 "type": "boolean",

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

782 },

783 "confidential_issues_events": {

784 "type": "boolean",

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

786 },

787 "merge_requests_events": {

788 "type": "boolean",

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

790 },

791 "tag_push_events": {

792 "type": "boolean",

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

794 },

795 "note_events": {

796 "type": "boolean",

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

798 },

799 "confidential_note_events": {

800 "type": "boolean",

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

802 },

803 "job_events": {

804 "type": "boolean",

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

806 },

807 "pipeline_events": {

808 "type": "boolean",

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

810 },

811 "wiki_page_events": {

812 "type": "boolean",

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

814 },

815 "enable_ssl_verification": {

816 "type": "boolean",

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

818 },

819 "token": {

820 "type": "string",

821 "description": (

822 "Secret token to validate received payloads; "

823 "this will not be returned in the response"

824 ),

825 },

826 },

827 "required": ["url"],

828 "additionalProperties": False,

829 },

830 "uniqueItems": True,

831 "_example": (

832 "\n"

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

834 " push_events: true\n"

835 " push_events_branch_filter: ''\n"

836 " issues_events: true\n"

837 " confidential_issues_events: true\n"

838 " merge_requests_events: true\n"

839 " tag_push_events: true\n"

840 " note_events: true\n"

841 " confidential_note_events: true\n"

842 " job_events: true\n"

843 " pipeline_events: true\n"

844 " wiki_page_events: true\n"

845 " enable_ssl_verification: true\n"

846 " # token: T0k3N\n"

847 ),

848 },

849 "unknown_webhooks": { # GitLabracadabra

850 "type": "string",

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

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

853 },

854 "default_branch": {

855 "type": "string",

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

857 },

858 "autoclose_referenced_issues": {

859 "type": "boolean",

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

861 },

862 "issue_branch_template": {

863 "type": "string",

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

865 },

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

867 "mirror": {

868 "type": "boolean",

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

870 },

871 "import_url": {

872 "type": "string",

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

874 },

875 "mirror_user_id": {

876 "type": "integer",

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

878 },

879 "mirror_overwrites_diverged_branches": {

880 "type": "boolean",

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

882 },

883 "mirror_trigger_builds": {

884 "type": "boolean",

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

886 },

887 "only_mirror_protected_branches": {

888 "type": "boolean",

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

890 },

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

892 "protected_branches": {

893 "type": "object",

894 "description": "Protected branches",

895 "additionalProperties": {

896 "type": "object",

897 "properties": {

898 "merge_access_level": {

899 "type": "string",

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

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

902 },

903 "allowed_to_merge": {

904 "type": "array",

905 "description": "",

906 "items": {

907 "oneOf": [

908 {

909 "type": "object",

910 "description": "Role",

911 "properties": {

912 "role": {

913 "type": "string",

914 "description": "Role name",

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

916 },

917 },

918 "additionalProperties": False,

919 },

920 {

921 "type": "object",

922 "description": "User",

923 "properties": {

924 "user": {

925 "type": "string",

926 "description": "User name",

927 },

928 },

929 "additionalProperties": False,

930 },

931 {

932 "type": "object",

933 "description": "Group",

934 "properties": {

935 "group": {

936 "type": "string",

937 "description": "Group full path",

938 },

939 },

940 "additionalProperties": False,

941 },

942 ],

943 },

944 },

945 "push_access_level": {

946 "type": "string",

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

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

949 },

950 "allowed_to_push": {

951 "type": "array",

952 "description": "",

953 "items": {

954 "oneOf": [

955 {

956 "type": "object",

957 "description": "Role",

958 "properties": {

959 "role": {

960 "type": "string",

961 "description": "Role name",

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

963 },

964 },

965 "additionalProperties": False,

966 },

967 {

968 "type": "object",

969 "description": "User",

970 "properties": {

971 "user": {

972 "type": "string",

973 "description": "User name",

974 },

975 },

976 "additionalProperties": False,

977 },

978 {

979 "type": "object",

980 "description": "Group",

981 "properties": {

982 "group": {

983 "type": "string",

984 "description": "Group full path",

985 },

986 },

987 "additionalProperties": False,

988 },

989 {

990 "type": "object",

991 "description": "Deploy Key",

992 "properties": {

993 "deploy_key": {

994 "type": "string",

995 "description": "Deploy key Title",

996 },

997 },

998 "additionalProperties": False,

999 },

1000 ],

1001 },

1002 },

1003 "allow_force_push": {

1004 "type": "boolean",

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

1006 },

1007 "code_owner_approval_required": {

1008 "type": "boolean",

1009 "description": (

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

1011 ),

1012 },

1013 "allowed_to_unprotect": {

1014 "type": "array",

1015 "description": "",

1016 "items": {

1017 "oneOf": [

1018 {

1019 "type": "object",

1020 "description": "Role",

1021 "properties": {

1022 "role": {

1023 "type": "string",

1024 "description": "Role name",

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

1026 },

1027 },

1028 "additionalProperties": False,

1029 },

1030 {

1031 "type": "object",

1032 "description": "User",

1033 "properties": {

1034 "user": {

1035 "type": "string",

1036 "description": "User name",

1037 },

1038 },

1039 "additionalProperties": False,

1040 },

1041 {

1042 "type": "object",

1043 "description": "Group",

1044 "properties": {

1045 "group": {

1046 "type": "string",

1047 "description": "Group full path",

1048 },

1049 },

1050 "additionalProperties": False,

1051 },

1052 ],

1053 },

1054 },

1055 },

1056 },

1057 "_example": (

1058 "\n"

1059 " main:\n"

1060 " allowed_to_merge::\n"

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

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

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

1064 " allowed_to_push::\n"

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

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

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

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

1069 " allow_force_push: false\n"

1070 " code_owner_approval_required: false # EE only\n"

1071 " allowed_to_unprotect: # EE only\n"

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

1073 " - user: my_username\n"

1074 " - group: my_group\n"

1075 " develop:\n"

1076 " merge_access_level: developer\n"

1077 " push_access_level: developer\n"

1078 ),

1079 },

1080 "unknown_protected_branches": { # GitLabracadabra

1081 "type": "string",

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

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

1084 },

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

1086 "protected_tags": {

1087 "type": "object",

1088 "description": "Protected tags",

1089 "additionalProperties": {

1090 "type": "string",

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

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

1093 },

1094 "_example": (

1095 "\n"

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

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

1098 ),

1099 },

1100 "unknown_protected_tags": { # GitLabracadabra

1101 "type": "string",

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

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

1104 },

1105 "merge_method": {

1106 "type": "string",

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

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

1109 },

1110 "merge_pipelines_enabled": {

1111 "type": "boolean",

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

1113 },

1114 "merge_trains_enabled": {

1115 "type": "boolean",

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

1117 },

1118 "resolve_outdated_diff_discussions": {

1119 "type": "boolean",

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

1121 },

1122 "printing_merge_request_link_enabled": {

1123 "type": "boolean",

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

1125 },

1126 "remove_source_branch_after_merge": {

1127 "type": "boolean",

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

1129 },

1130 "squash_option": {

1131 "type": "string",

1132 "description": "Squash option.",

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

1134 },

1135 "only_allow_merge_if_pipeline_succeeds": {

1136 "type": "boolean",

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

1138 },

1139 "allow_merge_on_skipped_pipeline": {

1140 "type": "boolean",

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

1142 },

1143 "only_allow_merge_if_all_discussions_are_resolved": {

1144 "type": "boolean",

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

1146 },

1147 "only_allow_merge_if_all_status_checks_passed": {

1148 "type": "boolean",

1149 "description": (

1150 "Indicates that merges of merge requests" "should be blocked unless all status checks have passed."

1151 ),

1152 },

1153 "suggestion_commit_message": {

1154 "type": "string",

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

1156 },

1157 "merge_commit_template": {

1158 "type": "string",

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

1160 },

1161 "squash_commit_template": {

1162 "type": "string",

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

1164 },

1165 "merge_requests_template": {

1166 "type": "string",

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

1168 },

1169 "approvals_before_merge": {

1170 "type": "integer",

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

1172 "multipleOf": 1,

1173 "minimum": 0,

1174 },

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

1176 "public_jobs": {

1177 "type": "boolean",

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

1179 },

1180 "auto_cancel_pending_pipelines": {

1181 "type": "string",

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

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

1184 },

1185 "ci_forward_deployment_enabled": {

1186 "type": "boolean",

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

1188 },

1189 "ci_forward_deployment_rollback_allowed": {

1190 "type": "boolean",

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

1192 },

1193 "ci_separated_caches": {

1194 "type": "boolean",

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

1196 },

1197 "ci_restrict_pipeline_cancellation_role": {

1198 "type": "string",

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

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

1201 },

1202 "ci_config_path": {

1203 "type": "string",

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

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

1206 },

1207 "build_git_strategy": {

1208 "type": "string",

1209 "description": "The Git strategy",

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

1211 },

1212 "ci_default_git_depth": {

1213 "type": "integer",

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

1215 },

1216 "build_timeout": {

1217 "type": "integer",

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

1219 },

1220 "ci_allow_fork_pipelines_to_run_in_parent_project": {

1221 "type": "boolean",

1222 "description": (

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

1224 ),

1225 },

1226 "auto_devops_enabled": {

1227 "type": "boolean",

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

1229 },

1230 "auto_devops_deploy_strategy": {

1231 "type": "string",

1232 "description": "Auto Deploy strategy",

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

1234 },

1235 "allow_pipeline_trigger_approve_deployment": {

1236 "type": "boolean",

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

1238 },

1239 "shared_runners_enabled": {

1240 "type": "boolean",

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

1242 },

1243 "group_runners_enabled": {

1244 "type": "boolean",

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

1246 },

1247 "keep_latest_artifact": {

1248 "type": "boolean",

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

1250 },

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

1252 "variables": {

1253 "type": "array",

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

1255 "items": {

1256 "type": "object",

1257 "properties": {

1258 "key": {

1259 "type": "string",

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

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

1262 "maxLength": 255,

1263 },

1264 "value": {

1265 "type": "string",

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

1267 },

1268 "description": {

1269 "type": "string",

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

1271 },

1272 "variable_type": {

1273 "type": "string",

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

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

1276 },

1277 "protected": {

1278 "type": "boolean",

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

1280 },

1281 "masked": {

1282 "type": "boolean",

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

1284 },

1285 "raw": {

1286 "type": "boolean",

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

1288 },

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

1290 "type": "string",

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

1292 },

1293 },

1294 "required": ["key"],

1295 "additionalProperties": False,

1296 },

1297 "uniqueItems": True,

1298 "_example": (

1299 "\n"

1300 " - key: DAST_DISABLED\n"

1301 " value: '1'\n"

1302 " description: Disabled SAST\n"

1303 " masked: false\n"

1304 " protected: false\n"

1305 " raw: false # Expand variables\n"

1306 " environment_scope: '*'\n"

1307 " variable_type: env_var\n"

1308 ),

1309 },

1310 "unknown_variables": { # GitLabracadabra

1311 "type": "string",

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

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

1314 },

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

1316 # container_expiration_policy_attributes

1317 "container_expiration_policy": {

1318 "type": "object",

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

1320 "properties": {

1321 "enabled": {

1322 "type": "boolean",

1323 },

1324 "cadence": {

1325 "type": "string",

1326 },

1327 "keep_n": {

1328 "type": "integer",

1329 },

1330 "name_regex_keep": {

1331 "type": "string",

1332 },

1333 "older_than": {

1334 "type": "string",

1335 },

1336 "name_regex_delete": {

1337 "type": "string",

1338 },

1339 },

1340 "required": ["enabled"],

1341 "additionalProperties": False,

1342 "_example": (

1343 "\n"

1344 " enabled: true\n"

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

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

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

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

1349 " name_regex_delete: '.*'\n"

1350 ),

1351 },

1352 # GitLabracadabra

1353 "mirrors": {

1354 "type": "array",

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

1356 "items": {

1357 "type": "object",

1358 "properties": {

1359 "url": {

1360 "type": "string",

1361 "description": "Repository URL",

1362 },

1363 "auth_id": {

1364 "type": "string",

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

1366 },

1367 "direction": {

1368 "type": "string",

1369 "description": "Mirror direction",

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

1371 },

1372 "skip_ci": {

1373 "type": "boolean",

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

1375 },

1376 "push_options": {

1377 "type": "array",

1378 "description": "Default push options",

1379 "items": {

1380 "type": "string",

1381 },

1382 },

1383 "branches": {

1384 "type": "array",

1385 "description": "The branches mapping",

1386 "items": {

1387 "type": "object",

1388 "properties": {

1389 "from": {

1390 "type": "string",

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

1392 },

1393 "to": {

1394 "type": "string",

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

1396 },

1397 "push_options": {

1398 "type": "array",

1399 "description": "Push options",

1400 "items": {

1401 "type": "string",

1402 },

1403 },

1404 },

1405 "required": ["from"],

1406 "additionalProperties": False,

1407 },

1408 "uniqueItems": True,

1409 },

1410 "tags": {

1411 "type": "array",

1412 "description": "The tags mapping",

1413 "items": {

1414 "type": "object",

1415 "properties": {

1416 "from": {

1417 "type": "string",

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

1419 },

1420 "to": {

1421 "type": "string",

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

1423 },

1424 "push_options": {

1425 "type": "array",

1426 "description": "Push options",

1427 "items": {

1428 "type": "string",

1429 },

1430 },

1431 },

1432 "required": ["from"],

1433 "additionalProperties": False,

1434 },

1435 "uniqueItems": True,

1436 },

1437 },

1438 "required": ["url"],

1439 "additionalProperties": False,

1440 },

1441 "uniqueItems": True,

1442 "_example": (

1443 "\n"

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

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

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

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

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

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

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

1451 " - from: main\n"

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

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

1454 " # Using regexps\n"

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

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

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

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

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

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

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

1462 ),

1463 "_doc_link": "mirrors.md",

1464 "x-gitlabracadabra-order": 10,

1465 },

1466 "package_mirrors": {

1467 "type": "array",

1468 "description": "Package image mirrors",

1469 "items": {

1470 "oneOf": [

1471 {

1472 "type": "object",

1473 "description": "Github source",

1474 "properties": {

1475 "enabled": {

1476 "type": "boolean",

1477 },

1478 "github": {

1479 "type": "object",

1480 "properties": {

1481 "full_name": {

1482 "type": "string",

1483 },

1484 "package_name": {

1485 "type": "string",

1486 },

1487 "tags": {

1488 "type": "array",

1489 "items": {

1490 "type": "string",

1491 },

1492 },

1493 "semver": {

1494 "type": "string",

1495 },

1496 "latest_release": {

1497 "type": "boolean",

1498 },

1499 "tarball": {

1500 "type": "boolean",

1501 },

1502 "zipball": {

1503 "type": "boolean",

1504 },

1505 "assets": {

1506 "type": "array",

1507 "items": {

1508 "type": "string",

1509 },

1510 },

1511 },

1512 "required": ["full_name"],

1513 "additionalProperties": False,

1514 },

1515 },

1516 "additionalProperties": False,

1517 },

1518 {

1519 "type": "object",

1520 "description": "Helm source",

1521 "properties": {

1522 "enabled": {

1523 "type": "boolean",

1524 },

1525 "helm": {

1526 "type": "object",

1527 "properties": {

1528 "repo_url": {

1529 "type": "string",

1530 },

1531 "package_name": {

1532 "type": "string",

1533 },

1534 "versions": {

1535 "type": "array",

1536 "items": {

1537 "type": "string",

1538 },

1539 },

1540 "semver": {

1541 "type": "string",

1542 },

1543 "limit": {

1544 "type": "integer",

1545 },

1546 "channel": {

1547 "type": "string",

1548 },

1549 },

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

1551 "additionalProperties": False,

1552 },

1553 },

1554 "additionalProperties": False,

1555 },

1556 {

1557 "type": "object",

1558 "description": "PULP_MANIFEST repository",

1559 "properties": {

1560 "enabled": {

1561 "type": "boolean",

1562 },

1563 "pulp_manifest": {

1564 "type": "object",

1565 "properties": {

1566 "url": {

1567 "type": "string",

1568 },

1569 "package_name": {

1570 "type": "string",

1571 },

1572 "package_version": {

1573 "type": "string",

1574 },

1575 },

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

1577 "additionalProperties": False,

1578 },

1579 },

1580 "additionalProperties": False,

1581 },

1582 {

1583 "type": "object",

1584 "description": "PyPI source",

1585 "properties": {

1586 "enabled": {

1587 "type": "boolean",

1588 },

1589 "pypi": {

1590 "type": "object",

1591 "properties": {

1592 "index_url": {

1593 "type": "string",

1594 },

1595 "requirements": {

1596 "oneOf": [

1597 {

1598 "type": "string",

1599 },

1600 {

1601 "type": "array",

1602 "items": {

1603 "type": "string",

1604 },

1605 },

1606 ],

1607 },

1608 },

1609 "required": ["requirements"],

1610 "additionalProperties": False,

1611 },

1612 },

1613 "additionalProperties": False,

1614 },

1615 {

1616 "type": "object",

1617 "description": "Raw source",

1618 "properties": {

1619 "enabled": {

1620 "type": "boolean",

1621 },

1622 "raw": {

1623 "type": "object",

1624 "properties": {

1625 "default_url": {

1626 "type": "string",

1627 },

1628 "default_package_name": {

1629 "type": "string",

1630 },

1631 "default_package_version": {

1632 "type": "string",

1633 },

1634 "package_files": {

1635 "type": "array",

1636 "items": {

1637 "type": "object",

1638 "properties": {

1639 "url": {

1640 "type": "string",

1641 },

1642 "package_name": {

1643 "type": "string",

1644 },

1645 "package_version": {

1646 "type": "string",

1647 },

1648 "file_name": {

1649 "type": "string",

1650 },

1651 },

1652 "additionalProperties": False,

1653 },

1654 },

1655 },

1656 "required": ["default_url"],

1657 "additionalProperties": False,

1658 },

1659 },

1660 "additionalProperties": False,

1661 },

1662 ],

1663 },

1664 "_example": (

1665 "\n"

1666 " - raw:\n"

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

1668 " default_package_name: docker\n"

1669 " default_package_version: '0'\n"

1670 ),

1671 "_doc_link": "package_mirrors.md",

1672 "x-gitlabracadabra-order": 10,

1673 },

1674 "image_mirrors": {

1675 "type": "array",

1676 "description": "Container image mirrors",

1677 "items": {

1678 "type": "object",

1679 "properties": {

1680 "enabled": {

1681 "type": "boolean",

1682 },

1683 "from": {

1684 "oneOf": [

1685 {

1686 "type": "string",

1687 "description": "The source image",

1688 "pattern": ".+",

1689 },

1690 {

1691 "type": "object",

1692 "properties": {

1693 "base": {

1694 "type": "string",

1695 },

1696 "repositories": {

1697 "type": "array",

1698 "items": {

1699 "type": "string",

1700 },

1701 },

1702 "tags": {

1703 "type": "array",

1704 "items": {

1705 "type": "string",

1706 },

1707 },

1708 },

1709 "required": ["repositories"],

1710 "additionalProperties": False,

1711 },

1712 ],

1713 },

1714 "to": {

1715 "oneOf": [

1716 {

1717 "type": "string",

1718 "description": "The destination image",

1719 },

1720 {

1721 "type": "object",

1722 "properties": {

1723 "base": {

1724 "type": "string",

1725 },

1726 "repository": {

1727 "type": "string",

1728 },

1729 "tag": {

1730 "type": "string",

1731 },

1732 },

1733 "additionalProperties": False,

1734 },

1735 ],

1736 },

1737 "semver": {

1738 "type": "string",

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

1740 },

1741 },

1742 "required": ["from"],

1743 "additionalProperties": False,

1744 },

1745 "_example": (

1746 "\n"

1747 " # Mirror debian:bookworm\n"

1748 " # ... to registry.example.org/mygroup/myproject/library/debian:bookworm:\n"

1749 " - from: 'debian:bookworm'\n"

1750 " # Overriding destination:\n"

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

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

1753 ),

1754 "_doc_link": "image_mirrors.md",

1755 "x-gitlabracadabra-order": 10,

1756 },

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

1758 # Deprecated

1759 "build_coverage_regex": {

1760 "type": "string",

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

1762 },

1763 "container_registry_enabled": {

1764 "type": "boolean",

1765 "description": (

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

1767 "Use container_registry_access_level instead."

1768 ),

1769 },

1770 "emails_disabled": {

1771 "type": "boolean",

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

1773 },

1774 "issues_enabled": {

1775 "type": "boolean",

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

1777 },

1778 "jobs_enabled": {

1779 "type": "boolean",

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

1781 },

1782 "merge_requests_enabled": {

1783 "type": "boolean",

1784 "description": (

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

1786 ),

1787 },

1788 "public_builds": {

1789 "type": "boolean",

1790 "description": (

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

1792 ),

1793 },

1794 "snippets_enabled": {

1795 "type": "boolean",

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

1797 },

1798 "tag_list": {

1799 "type": "array",

1800 "description": (

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

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

1803 ),

1804 "items": {

1805 "type": "string",

1806 },

1807 "uniqueItems": True,

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

1809 },

1810 "wiki_enabled": {

1811 "type": "boolean",

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

1813 },

1814 # Below are undocumented settings

1815 "repository_storage": {

1816 "type": "string",

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

1818 },

1819 "external_authorization_classification_label": {

1820 "type": "string",

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

1822 },

1823 },

1824 "additionalProperties": False,

1825 }

1826

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

1828 "initialize_with_readme",

1829 "repository_object_format",

1830 "unknown_boards",

1831 "unknown_board_lists",

1832 "unknown_groups",

1833 "unknown_labels",

1834 "unknown_members",

1835 "unknown_milestones",

1836 "unknown_pipeline_schedules",

1837 "unknown_pipeline_schedule_variables",

1838 "unknown_protected_branches",

1839 "unknown_protected_tags",

1840 "unknown_variables",

1841 "unknown_webhooks",

1842 ]

1843

1844 CREATE_KEY = "name"

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

1846

1847 def _get_current_branches(self):

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

1849 try:

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

1851 except GitlabListError as err:

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

1853 pass

1854 self._current_branches = None

1855 return self._current_branches

1856

1857 """"_process_archived()

1858

1859 Process the archived param.

1860 """

1861

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

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

1864 assert not skip_save # noqa: S101

1865

1866 current_value = getattr(self._obj, param_name)

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

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

1869 logger.info(

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

1871 )

1872 setattr(self._obj, param_name, param_value)

1873 else:

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

1875 if param_value:

1876 self._obj.archive()

1877 else:

1878 self._obj.unarchive()

1879

1880 """"_process_branches()

1881

1882 Process the branches param.

1883 """

1884

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

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

1887 assert not skip_save # noqa: S101

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

1889 # Create from target default branch if it exists

1890 ref = self._content["default_branch"]

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

1892 # Create from current default branch otherwise

1893 ref = self._obj.default_branch

1894 else:

1895 ref = None

1896 for branch_name in param_value:

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

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

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

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

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

1902 self._current_branches.append(branch_name)

1903 else:

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

1905 self._obj.branches.create(

1906 {

1907 "branch": branch_name,

1908 "ref": ref,

1909 }

1910 )

1911 self._current_branches.append(branch_name)

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

1913 # Next branch will be created from this ref

1914 ref = branch_name

1915

1916 """"_process_protected_tags()

1917

1918 Process the protected_tags param.

1919 """

1920

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

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

1923 assert not skip_save # noqa: S101

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

1925 try:

1926 current_protected_tags = dict(

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

1928 )

1929 except AttributeError:

1930 logger.error(

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

1932 )

1933 return

1934 # We first check for already protected tags

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

1936 target_config = {

1937 "name": protected_name,

1938 "create_access_level": access_level_value(target_config),

1939 }

1940 if protected_name in current_protected_tags:

1941 current_protected_tag = current_protected_tags[protected_name]

1942 current_config = {

1943 "name": protected_name,

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

1945 }

1946 else:

1947 current_config = {}

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

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

1950 logger.info(

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

1952 self._name,

1953 protected_name,

1954 current_config,

1955 target_config,

1956 )

1957 else:

1958 logger.info(

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

1960 self._name,

1961 protected_name,

1962 current_config,

1963 target_config,

1964 )

1965 if "name" in current_config:

1966 self._obj.protectedtags.delete(protected_name)

1967 self._obj.protectedtags.create(target_config)

1968 # Remaining protected tags

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

1970 current_protected_tags = sorted(

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

1972 )

1973 for protected_name in current_protected_tags:

1974 if protected_name not in param_value:

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

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

1977 logger.info(

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

1979 )

1980 else:

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

1982 self._obj.protectedtags.delete(protected_name)

1983 else:

1984 logger.warning(

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

1986 self._name,

1987 protected_name,

1988 unknown_protected_tags,

1989 )

1990

1991 """"_process_container_expiration_policy()

1992

1993 Process the container_expiration_policy param.

1994 """

1995

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

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

1998 assert not skip_save # noqa: S101

1999

2000 current_value = getattr(self._obj, param_name)

2001

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

2003 current_v = current_value.get(k, None)

2004 if v != current_v:

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

2006 logger.info(

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

2008 self._name,

2009 k,

2010 current_v,

2011 v,

2012 )

2013 else:

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

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

2016 self._obj.save()