Coverage for src/cli/entrypoint.py: 6.81%

382 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 21:20 +0000

1#!/usr/bin/env python3 

2 

3# Standard libraries 

4from argparse import Namespace 

5from enum import Enum 

6import json 

7from typing import Dict, List, Optional 

8import urllib.parse 

9 

10# Modules libraries 

11from gitlab.config import ConfigError, GitlabConfigParser 

12from gitlab.exceptions import GitlabGetError 

13from gitlab.v4.objects import ( 

14 Group as GitLabGroup, 

15 Project as GitLabProject, 

16 User as GitLabUser, 

17) 

18import questionary 

19 

20# Components 

21from ..features.gitlab import GitLabFeature 

22from ..prints.colors import Colors 

23from ..prints.themes import Themes 

24from ..system.platform import Platform 

25from ..types.environments import Environments 

26from ..types.gitlab import ( 

27 MergeRequestsMethod, 

28 MergeRequestsPipelines, 

29 MergeRequestsResolved, 

30 MergeRequestsSkipped, 

31 MergeRequestsSquash, 

32 ProjectFeatures, 

33) 

34from ..types.namespaces import Namespaces 

35 

36# Entrypoint class, pylint: disable=too-few-public-methods 

37class Entrypoint: 

38 

39 # Enumerations 

40 Result = Enum('Result', [ 

41 'SUCCESS', 

42 'FINALIZE', 

43 'ERROR', 

44 'CRITICAL', 

45 ]) 

46 

47 # CLI, pylint: disable=too-many-branches,too-many-locals,too-many-statements 

48 @staticmethod 

49 def cli( 

50 options: Namespace, 

51 environments: Environments, 

52 ) -> Result: 

53 

54 # Variables 

55 group: Optional[GitLabGroup] = None 

56 progress_count: int 

57 progress_index: int 

58 project: Optional[GitLabProject] = None 

59 result: Entrypoint.Result 

60 user: Optional[GitLabUser] = None 

61 

62 # Header 

63 print(' ') 

64 

65 # Parse URL variables 

66 gitlab_splits: urllib.parse.SplitResult = urllib.parse.urlsplit(options.url_path) 

67 gitlab_id: str = f'{gitlab_splits.netloc}' 

68 gitlab_url: str = f'{gitlab_splits.scheme}://{gitlab_splits.netloc}' 

69 gitlab_path: str = gitlab_splits.path.lstrip('/') 

70 

71 # Prepare credentials 

72 private_token: str = environments.value('gitlab_token') 

73 job_token: str = environments.value('ci_job_token') 

74 ssl_verify: bool | str = True 

75 

76 # Parse configuration files 

77 try: 

78 config: GitlabConfigParser 

79 if not private_token: 

80 config = GitlabConfigParser(gitlab_id, options.configs) 

81 private_token = str(config.private_token) 

82 if ssl_verify and (not config.ssl_verify 

83 or isinstance(config.ssl_verify, str)): 

84 ssl_verify = config.ssl_verify 

85 except ConfigError as e: 

86 print(str(e)) 

87 

88 # GitLab client 

89 gitlab = GitLabFeature( 

90 url=gitlab_url, 

91 private_token=private_token, 

92 job_token=job_token, 

93 ssl_verify=ssl_verify, 

94 dry_run=options.dry_run, 

95 ) 

96 print(f'{Colors.BOLD} - GitLab host: ' 

97 f'{Colors.GREEN}{gitlab.url}' 

98 f'{Colors.CYAN} ({gitlab.username})' 

99 f'{Colors.RESET}') 

100 Platform.flush() 

101 

102 # GitLab path 

103 try: 

104 group = gitlab.group(gitlab_path) 

105 print(f'{Colors.BOLD} - GitLab group: ' 

106 f'{Colors.GREEN}{group.full_path}' 

107 f'{Colors.CYAN} # {group.description if group.description else "/"}' 

108 f'{Colors.RESET}') 

109 print(' ') 

110 Platform.flush() 

111 except GitlabGetError as exception: 

112 try: 

113 if '/' in gitlab_path: 

114 raise TypeError from exception 

115 user = gitlab.user(gitlab_path) 

116 namespace = gitlab.namespace(gitlab_path) 

117 print(f'{Colors.BOLD} - GitLab user namespace: ' 

118 f'{Colors.GREEN}{namespace.full_path}' 

119 f'{Colors.CYAN} ({namespace.name})' 

120 f'{Colors.RESET}') 

121 print(' ') 

122 Platform.flush() 

123 except (GitlabGetError, TypeError): 

124 project = gitlab.project(gitlab_path) 

125 print( 

126 f'{Colors.BOLD} - GitLab project: ' 

127 f'{Colors.GREEN}{project.path_with_namespace}' 

128 f'{Colors.CYAN} # {project.description if project.description else "/"}' 

129 f'{Colors.RESET}') 

130 print(' ') 

131 Platform.flush() 

132 

133 # Handle available features 

134 if options.available_features: 

135 print(f'{Colors.BOLD} - GitLab project:' 

136 f'{Colors.RESET}') 

137 print(f'{Colors.BOLD} - Available features: ' 

138 f'{Colors.CYAN}\'{", ".join(ProjectFeatures.names())}\'' 

139 f'{Colors.RESET}') 

140 Platform.flush() 

141 return Entrypoint.Result.FINALIZE 

142 

143 # Handle single project 

144 if project: 

145 Entrypoint.project( 

146 options, 

147 gitlab, 

148 project.path_with_namespace, 

149 ) 

150 

151 # Handle group recursively 

152 elif group: 

153 

154 # Handle group 

155 if not options.exclude_group: 

156 result = Entrypoint.group( 

157 options, 

158 gitlab, 

159 group.full_path, 

160 ) 

161 if result in [ 

162 Entrypoint.Result.FINALIZE, 

163 Entrypoint.Result.ERROR, 

164 ]: 

165 return result 

166 

167 # Iterate through subgroups 

168 if not options.exclude_subgroups: 

169 group_subgroups = sorted( 

170 group.descendant_groups.list( 

171 get_all=True, 

172 include_subgroups=True, 

173 order_by='path', 

174 sort='asc', 

175 ), 

176 key=lambda item: item.full_path, 

177 ) 

178 progress_count = len(group_subgroups) 

179 progress_index = 0 

180 for group_subgroup in group_subgroups: 

181 progress_index += 1 

182 result = Entrypoint.group( 

183 options, 

184 gitlab, 

185 group_subgroup.full_path, 

186 True, 

187 progress_index=progress_index, 

188 progress_count=progress_count, 

189 ) 

190 if result in [ 

191 Entrypoint.Result.FINALIZE, 

192 Entrypoint.Result.ERROR, 

193 ]: 

194 return result 

195 

196 # Iterate through projects 

197 if not options.exclude_projects: 

198 projects = sorted( 

199 group.projects.list( 

200 get_all=True, 

201 with_shared=False, 

202 include_subgroups=not options.exclude_subgroups, 

203 order_by='path', 

204 sort='asc', 

205 ), 

206 key=lambda item: item.path_with_namespace, 

207 ) 

208 progress_count = len(projects) 

209 progress_index = 0 

210 for group_project in projects: 

211 progress_index += 1 

212 result = Entrypoint.project( 

213 options, 

214 gitlab, 

215 group_project.path_with_namespace, 

216 progress_index=progress_index, 

217 progress_count=progress_count, 

218 ) 

219 if result in [ 

220 Entrypoint.Result.FINALIZE, 

221 Entrypoint.Result.ERROR, 

222 ]: 

223 return result 

224 

225 # Handle user recursively 

226 elif user: 

227 

228 # Iterate through projects 

229 if not options.exclude_projects: 

230 user_projects = sorted( 

231 user.projects.list( 

232 get_all=True, 

233 order_by='path', 

234 sort='asc', 

235 ), 

236 key=lambda item: item.path_with_namespace, 

237 ) 

238 progress_count = len(user_projects) 

239 progress_index = 0 

240 for user_project in user_projects: 

241 progress_index += 1 

242 result = Entrypoint.project( 

243 options, 

244 gitlab, 

245 user_project.path_with_namespace, 

246 progress_index=progress_index, 

247 progress_count=progress_count, 

248 ) 

249 if result in [ 

250 Entrypoint.Result.FINALIZE, 

251 Entrypoint.Result.ERROR, 

252 ]: 

253 return result 

254 

255 # Result 

256 return Entrypoint.Result.SUCCESS 

257 

258 # Confirm 

259 @staticmethod 

260 def confirm( 

261 description: str, 

262 text: str = '', 

263 interactive: bool = True, 

264 action: str = '', 

265 indent: str = ' ', 

266 ) -> bool: 

267 

268 # Header 

269 print( 

270 f'{Colors.BOLD}{indent}- {description}{": " if description else ""}Confirm \'' 

271 f'{Colors.RED}{text}' 

272 f'{Colors.BOLD}\' {action}:' 

273 f'{Colors.RESET}', end='') 

274 Platform.flush() 

275 

276 # Confirm without user interaction 

277 if not interactive: 

278 print(f'{Colors.RED} Confirmed by parameters' 

279 f'{Colors.RESET}') 

280 Platform.flush() 

281 return True 

282 

283 # Get user configuration 

284 answer: bool = questionary.confirm( 

285 message='', 

286 default=False, 

287 qmark='', 

288 style=Themes.confirmation_style(), 

289 auto_enter=True, 

290 ).ask() 

291 

292 # Result 

293 return answer 

294 

295 # Group, pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments 

296 @staticmethod 

297 def group( 

298 options: Namespace, 

299 gitlab: GitLabFeature, 

300 criteria: str, 

301 subgroup: bool = False, 

302 progress_index: int = 1, 

303 progress_count: int = 1, 

304 ) -> Result: 

305 

306 # Variables 

307 changed: bool 

308 

309 # Acquire group 

310 group = gitlab.group(criteria) 

311 

312 # Acquire parent group 

313 parent_group: Optional[GitLabGroup] = None 

314 if group.parent_id: 

315 parent_group = gitlab.group(group.parent_id) 

316 

317 # Get parent description 

318 parent_description: str = '' 

319 parent_name: str = '' 

320 if parent_group: 

321 parent_description = parent_group.description 

322 parent_name = parent_group.name 

323 

324 # Show group details 

325 group_type = 'subgroup' if subgroup else 'group' 

326 if subgroup: 

327 print(f'{Colors.BOLD} - GitLab {group_type} (' 

328 f'{Colors.GREEN}{progress_index}' 

329 f'{Colors.RESET}/' 

330 f'{Colors.CYAN}{progress_count}' 

331 f'{Colors.BOLD}) : ' 

332 f'{Colors.YELLOW_LIGHT}{group.full_path}' 

333 f'{Colors.CYAN} # {group.description if group.description else "/"}' 

334 f'{Colors.RESET}') 

335 else: 

336 print(f'{Colors.BOLD} - GitLab {group_type}: ' 

337 f'{Colors.YELLOW_LIGHT}{group.full_path}' 

338 f'{Colors.CYAN} # {group.description if group.description else "/"}' 

339 f'{Colors.RESET}') 

340 Platform.flush() 

341 

342 # Delete group after validation 

343 if options.delete_group: 

344 if not Entrypoint.confirm( 

345 f'Delete {group_type}', 

346 group.full_path, 

347 not options.confirm, 

348 'deletion', 

349 ): 

350 print(' ') 

351 Platform.flush() 

352 return Entrypoint.Result.SUCCESS 

353 

354 # Delete group 

355 gitlab.group_delete(criteria) 

356 print(f'{Colors.BOLD} - Delete {group_type}: ' 

357 f'{Colors.GREEN}Success' 

358 f'{Colors.RESET}') 

359 print(' ') 

360 Platform.flush() 

361 return Entrypoint.Result.SUCCESS if subgroup else Entrypoint.Result.FINALIZE 

362 

363 # Set group description 

364 if options.set_description: 

365 changed = gitlab.group_set_description( 

366 criteria, 

367 options.set_description, 

368 ) 

369 print(f'{Colors.BOLD} - Set description: ' 

370 f'{Colors.CYAN if changed else Colors.GREEN}{options.set_description}' 

371 f'{Colors.RESET}') 

372 Platform.flush() 

373 

374 # Update group description 

375 elif options.update_description: 

376 parent_description_text: str = '' 

377 if parent_name: 

378 parent_description_text = ' - ' + Namespaces.describe( 

379 name=parent_name, 

380 description=parent_description, 

381 ) 

382 if not group.description or subgroup and ( \ 

383 not parent_description_text or \ 

384 not group.description.endswith(f'{parent_description_text}') \ 

385 ): 

386 description = f'{Namespaces.describe(name=group.name)}' \ 

387 f'{parent_description_text}' 

388 changed = gitlab.group_set_description( 

389 criteria, 

390 description, 

391 ) 

392 print(f'{Colors.BOLD} - Updated description: ' 

393 f'{Colors.CYAN if changed else Colors.GREEN}{description}' 

394 f'{Colors.RESET}') 

395 Platform.flush() 

396 else: 

397 print(f'{Colors.BOLD} - Kept description: ' 

398 f'{Colors.GREEN}{group.description}' 

399 f'{Colors.RESET}') 

400 Platform.flush() 

401 

402 # Reset group members 

403 if options.reset_members: 

404 gitlab.group_reset_members(criteria) 

405 print(f'{Colors.BOLD} - Reset members: ' 

406 f'{Colors.GREEN}Success' 

407 f'{Colors.RESET}') 

408 Platform.flush() 

409 

410 # Set group avatar 

411 if options.set_avatar: 

412 gitlab.group_set_avatar( 

413 criteria, 

414 options.set_avatar, 

415 ) 

416 print(f'{Colors.BOLD} - Set avatar: ' 

417 f'{Colors.CYAN}{options.set_avatar}' 

418 f'{Colors.RESET}') 

419 Platform.flush() 

420 

421 # Set group roles create projects 

422 if options.set_roles_create_projects: 

423 changed = gitlab.group_set_roles_create_projects( 

424 criteria, 

425 options.set_roles_create_projects, 

426 ) 

427 print( 

428 f'{Colors.BOLD} - Set roles allowed to create projects: ' 

429 f'{Colors.CYAN if changed else Colors.GREEN}{options.set_roles_create_projects}' 

430 f'{Colors.RESET}') 

431 Platform.flush() 

432 

433 # Set group roles create subgroups 

434 if options.set_roles_create_subgroups: 

435 changed = gitlab.group_set_roles_create_subgroups( 

436 criteria, 

437 options.set_roles_create_subgroups, 

438 ) 

439 print( 

440 f'{Colors.BOLD} - Set roles allowed to create subgroups: ' 

441 f'{Colors.CYAN if changed else Colors.GREEN}{options.set_roles_create_subgroups}' 

442 f'{Colors.RESET}') 

443 Platform.flush() 

444 

445 # Get group labels 

446 if options.get_group_labels: 

447 labels_objects = gitlab.group_get_labels(criteria) 

448 if labels_objects: 

449 print(f'{Colors.BOLD} - Get labels to JSON: ' 

450 f'{Colors.RESET}', end='') 

451 labels_list: List[Dict[str, str]] = [] 

452 for label in labels_objects: 

453 labels_list += [{ 

454 'name': label.name, 

455 'description': label.description, 

456 'text_color': label.text_color, 

457 'color': label.color, 

458 }] 

459 print(json.dumps(labels_list)) 

460 Platform.flush() 

461 

462 # Set group labels 

463 elif options.set_group_labels: 

464 print(f'{Colors.BOLD} - Set labels from JSON: ' 

465 f'{Colors.RESET}', end='') 

466 labels_names: List[str] = [] 

467 labels_json = json.loads(options.set_group_labels) 

468 for label_json in labels_json: 

469 label_set = gitlab.group_set_label( 

470 criteria, 

471 name=label_json['name'], 

472 description=label_json['description'], 

473 text_color=label_json['text_color'], 

474 color=label_json['color'], 

475 ) 

476 labels_names += [label_set.name] 

477 print(f'{Colors.CYAN}{", ".join(labels_names)}' 

478 f'{Colors.RESET}') 

479 Platform.flush() 

480 

481 # Dump group object 

482 if options.dump: 

483 print(' ') 

484 print(group.to_json()) 

485 

486 # Footer 

487 print(' ') 

488 Platform.flush() 

489 

490 # Result 

491 return Entrypoint.Result.SUCCESS 

492 

493 # Project, pylint: disable=too-many-branches,too-many-locals,too-many-statements 

494 @staticmethod 

495 def project( 

496 options: Namespace, 

497 gitlab: GitLabFeature, 

498 criteria: str, 

499 progress_index: int = 1, 

500 progress_count: int = 1, 

501 ) -> Result: 

502 

503 # Variables 

504 changed: Optional[bool] 

505 

506 # Acquire project 

507 project = gitlab.project(criteria) 

508 

509 # Show project details 

510 print(f'{Colors.BOLD} - GitLab project (' 

511 f'{Colors.GREEN}{progress_index}' 

512 f'{Colors.RESET}/' 

513 f'{Colors.CYAN}{progress_count}' 

514 f'{Colors.BOLD}) : ' 

515 f'{Colors.YELLOW_LIGHT}{project.path_with_namespace}' 

516 f'{Colors.CYAN} # {project.description if project.description else "/"}' 

517 f'{Colors.RESET}') 

518 Platform.flush() 

519 

520 # Delete project after validation 

521 if options.delete_project: 

522 if not Entrypoint.confirm( 

523 'Delete project', 

524 project.path_with_namespace, 

525 not options.confirm, 

526 'deletion', 

527 ): 

528 print(' ') 

529 Platform.flush() 

530 return Entrypoint.Result.SUCCESS 

531 

532 # Delete project 

533 gitlab.project_delete(criteria) 

534 print(f'{Colors.BOLD} - Delete project: ' 

535 f'{Colors.GREEN}Success' 

536 f'{Colors.RESET}') 

537 print(' ') 

538 Platform.flush() 

539 return Entrypoint.Result.SUCCESS 

540 

541 # Set project description 

542 if options.set_description: 

543 changed = gitlab.project_set_description( 

544 criteria, 

545 options.set_description, 

546 ) 

547 print(f'{Colors.BOLD} - Set description: ' 

548 f'{Colors.CYAN if changed else Colors.GREEN}{options.set_description}' 

549 f'{Colors.RESET}') 

550 Platform.flush() 

551 

552 # Update project description 

553 elif options.update_description: 

554 namespace_description: str 

555 if project.namespace['kind'] == 'user': 

556 namespace = gitlab.namespace(project.namespace['id']) 

557 namespace_description = namespace.name 

558 else: 

559 group = gitlab.group(project.namespace['id']) 

560 namespace_description = Namespaces.describe( 

561 name=group.name, 

562 description=group.description, 

563 ) 

564 if not project.description or \ 

565 not project.description.endswith(f' - {namespace_description}'): 

566 description = f'{Namespaces.describe(name=project.name)}' \ 

567 f' - {namespace_description}' 

568 changed = gitlab.project_set_description( 

569 criteria, 

570 description, 

571 ) 

572 print(f'{Colors.BOLD} - Updated description: ' 

573 f'{Colors.CYAN if changed else Colors.GREEN}{description}' 

574 f'{Colors.RESET}') 

575 Platform.flush() 

576 else: 

577 print(f'{Colors.BOLD} - Kept description: ' 

578 f'{Colors.GREEN}{project.description}' 

579 f'{Colors.RESET}') 

580 Platform.flush() 

581 

582 # Reset project features 

583 if options.reset_features is not None: 

584 features = ', '.join( 

585 gitlab.project_features_reset( 

586 criteria, 

587 GitLabFeature.project_features_parse(options.reset_features), 

588 )) 

589 if features: 

590 print(f'{Colors.BOLD} - Reset features: ' 

591 f'{Colors.CYAN}{features}' 

592 f'{Colors.RESET}') 

593 Platform.flush() 

594 else: 

595 print(f'{Colors.BOLD} - Reset features: ' 

596 f'{Colors.GREEN}Already done' 

597 f'{Colors.RESET}') 

598 Platform.flush() 

599 

600 # Disable project features 

601 if options.disable_features: 

602 features = ', '.join( 

603 gitlab.project_features_disable( 

604 criteria, 

605 GitLabFeature.project_features_parse(options.disable_features), 

606 )) 

607 if features: 

608 print(f'{Colors.BOLD} - Disable features: ' 

609 f'{Colors.CYAN}{features}' 

610 f'{Colors.RESET}') 

611 Platform.flush() 

612 else: 

613 print(f'{Colors.BOLD} - Disable features: ' 

614 f'{Colors.GREEN}Already done' 

615 f'{Colors.RESET}') 

616 Platform.flush() 

617 

618 # Enable project features 

619 if options.enable_features: 

620 features = ', '.join( 

621 gitlab.project_features_enable( 

622 criteria, 

623 GitLabFeature.project_features_parse(options.enable_features), 

624 )) 

625 if features: 

626 print(f'{Colors.BOLD} - Enable features: ' 

627 f'{Colors.CYAN}{features}' 

628 f'{Colors.RESET}') 

629 Platform.flush() 

630 else: 

631 print(f'{Colors.BOLD} - Enable features: ' 

632 f'{Colors.GREEN}Already done' 

633 f'{Colors.RESET}') 

634 Platform.flush() 

635 

636 # Reset project members 

637 if options.reset_members: 

638 gitlab.project_reset_members(criteria) 

639 print(f'{Colors.BOLD} - Reset members: ' 

640 f'{Colors.GREEN}Success' 

641 f'{Colors.RESET}') 

642 Platform.flush() 

643 

644 # Set project avatar 

645 if options.set_avatar: 

646 gitlab.project_set_avatar( 

647 criteria, 

648 options.set_avatar, 

649 ) 

650 print(f'{Colors.BOLD} - Set avatar: ' 

651 f'{Colors.CYAN}{options.set_avatar}' 

652 f'{Colors.RESET}') 

653 Platform.flush() 

654 

655 # Run project housekeeping 

656 if options.run_housekeeping: 

657 gitlab.project_run_housekeeping(criteria) 

658 print(f'{Colors.BOLD} - Ran housekeeping: ' 

659 f'{Colors.GREEN}Success' 

660 f'{Colors.RESET}') 

661 Platform.flush() 

662 

663 # Prune project unreachable objects 

664 if options.prune_unreachable_objects: 

665 gitlab.project_prune_unreachable_objects(criteria) 

666 print(f'{Colors.BOLD} - Pruned unreachable objects: ' 

667 f'{Colors.GREEN}Success' 

668 f'{Colors.RESET}') 

669 Platform.flush() 

670 

671 # Archive project 

672 if options.archive_project: 

673 changed = gitlab.project_set_archive( 

674 criteria, 

675 True, 

676 ) 

677 print(f'{Colors.BOLD} - Archive project: ' 

678 f'{Colors.CYAN if changed else Colors.GREEN}' 

679 f'{"Success" if changed else "Already done"}' 

680 f'{Colors.RESET}') 

681 Platform.flush() 

682 

683 # Unarchive project 

684 elif options.unarchive_project: 

685 changed = gitlab.project_set_archive( 

686 criteria, 

687 False, 

688 ) 

689 print(f'{Colors.BOLD} - Unarchive project: ' 

690 f'{Colors.CYAN if changed else Colors.GREEN}' 

691 f'{"Success" if changed else "Already done"}' 

692 f'{Colors.RESET}') 

693 Platform.flush() 

694 

695 # Show archived project 

696 elif project.archived: 

697 print(f'{Colors.BOLD} - Project inactive: ' 

698 f'{Colors.YELLOW_LIGHT}Archived' 

699 f'{Colors.RESET}') 

700 Platform.flush() 

701 

702 # Protect project branches 

703 if options.protect_branches: 

704 branches = ', '.join(gitlab.project_protect_branches(criteria)) 

705 if branches: 

706 print(f'{Colors.BOLD} - Protecting branches: ' 

707 f'{Colors.CYAN}{branches}' 

708 f'{Colors.RESET}') 

709 Platform.flush() 

710 else: 

711 print(f'{Colors.BOLD} - Protecting branches: ' 

712 f'{Colors.GREEN}Already done' 

713 f'{Colors.RESET}') 

714 Platform.flush() 

715 

716 # Protect project tags 

717 if options.protect_tags: 

718 tags = ', '.join(gitlab.project_protect_tags(criteria, options.protect_tags)) 

719 if tags: 

720 print(f'{Colors.BOLD} - Protecting tags: ' 

721 f'{Colors.CYAN}{tags}' 

722 f'{Colors.GREEN} (level: {options.protect_tags})' 

723 f'{Colors.RESET}') 

724 Platform.flush() 

725 else: 

726 print(f'{Colors.BOLD} - Protecting tags: ' 

727 f'{Colors.GREEN}Already done' 

728 f'{Colors.RESET}') 

729 Platform.flush() 

730 

731 # Set project merge method 

732 if options.set_merge_method is not None: 

733 changed = gitlab.project_set_attribute( 

734 criteria, 

735 MergeRequestsMethod.ATTRIBUTE, 

736 MergeRequestsMethod.VALUES[options.set_merge_method], 

737 ) 

738 if changed is None: 

739 print(f'{Colors.BOLD} - Set merge method: ' 

740 f'{Colors.RED}Unknown feature' 

741 f'{Colors.RESET}') 

742 else: 

743 print(f'{Colors.BOLD} - Set merge method: ' 

744 f'{Colors.CYAN if changed else Colors.GREEN}' 

745 f'{options.set_merge_method}' 

746 f'{Colors.RESET}') 

747 Platform.flush() 

748 

749 # Set project merge squash 

750 if options.set_merge_squash is not None: 

751 changed = gitlab.project_set_attribute( 

752 criteria, 

753 MergeRequestsSquash.ATTRIBUTE, 

754 MergeRequestsSquash.VALUES[options.set_merge_squash], 

755 ) 

756 if changed is None: 

757 print(f'{Colors.BOLD} - Set merge squash: ' 

758 f'{Colors.RED}Unknown feature' 

759 f'{Colors.RESET}') 

760 else: 

761 print(f'{Colors.BOLD} - Set merge squash: ' 

762 f'{Colors.CYAN if changed else Colors.GREEN}' 

763 f'{options.set_merge_squash}' 

764 f'{Colors.RESET}') 

765 Platform.flush() 

766 

767 # Set project merge pipelines 

768 if options.set_merge_pipelines is not None: 

769 changed = gitlab.project_set_attribute( 

770 criteria, 

771 MergeRequestsPipelines.ATTRIBUTE, 

772 options.set_merge_pipelines, 

773 ) 

774 if changed is None: 

775 print(f'{Colors.BOLD} - Set merge pipelines: ' 

776 f'{Colors.RED}Unknown feature' 

777 f'{Colors.RESET}') 

778 else: 

779 print(f'{Colors.BOLD} - Set merge pipelines: ' 

780 f'{Colors.CYAN if changed else Colors.GREEN}' 

781 f'{str(options.set_merge_pipelines).lower()}' 

782 f'{Colors.RESET}') 

783 Platform.flush() 

784 

785 # Set project merge skipped 

786 if options.set_merge_skipped is not None: 

787 changed = gitlab.project_set_attribute( 

788 criteria, 

789 MergeRequestsSkipped.ATTRIBUTE, 

790 options.set_merge_skipped, 

791 ) 

792 if changed is None: 

793 print(f'{Colors.BOLD} - Set merge skipped: ' 

794 f'{Colors.RED}Unknown feature' 

795 f'{Colors.RESET}') 

796 else: 

797 print(f'{Colors.BOLD} - Set merge skipped: ' 

798 f'{Colors.CYAN if changed else Colors.GREEN}' 

799 f'{str(options.set_merge_skipped).lower()}' 

800 f'{Colors.RESET}') 

801 Platform.flush() 

802 

803 # Set project merge resolved 

804 if options.set_merge_resolved is not None: 

805 changed = gitlab.project_set_attribute( 

806 criteria, 

807 MergeRequestsResolved.ATTRIBUTE, 

808 options.set_merge_resolved, 

809 ) 

810 if changed is None: 

811 print(f'{Colors.BOLD} - Set merge method: ' 

812 f'{Colors.RED}Unknown feature' 

813 f'{Colors.RESET}') 

814 else: 

815 print(f'{Colors.BOLD} - Set merge method: ' 

816 f'{Colors.CYAN if changed else Colors.GREEN}' 

817 f'{str(options.set_merge_resolved).lower()}' 

818 f'{Colors.RESET}') 

819 Platform.flush() 

820 

821 # Add path to CI/CD job token allowlist 

822 if options.add_jobs_token_allowlist: 

823 gitlab.project_job_allowlist_add( 

824 criteria, 

825 options.add_jobs_token_allowlist, 

826 ) 

827 print(f'{Colors.BOLD} - Added to CI/CD job token allowlist: ' 

828 f'{Colors.CYAN}{options.add_jobs_token_allowlist}' 

829 f'{Colors.RESET}') 

830 Platform.flush() 

831 

832 # Remove path from CI/CD job token allowlist 

833 if options.remove_jobs_token_allowlist: 

834 gitlab.project_job_allowlist_remove( 

835 criteria, 

836 options.remove_jobs_token_allowlist, 

837 ) 

838 print(f'{Colors.BOLD} - Removed from CI/CD job token allowlist: ' 

839 f'{Colors.CYAN}{options.remove_jobs_token_allowlist}' 

840 f'{Colors.RESET}') 

841 Platform.flush() 

842 

843 # Erase project jobs artifacts and traces 

844 if options.erase_jobs_contents: 

845 gitlab.project_erase_jobs_contents(criteria) 

846 print(f'{Colors.BOLD} - Erased CI/CD jobs artifacts and traces: ' 

847 f'{Colors.GREEN}Success' 

848 f'{Colors.RESET}') 

849 Platform.flush() 

850 

851 # Erase project jobs artifacts 

852 elif options.erase_jobs_artifacts: 

853 gitlab.project_erase_jobs_artifacts(criteria) 

854 print(f'{Colors.BOLD} - Erased CI/CD jobs artifacts: ' 

855 f'{Colors.GREEN}Success' 

856 f'{Colors.RESET}') 

857 Platform.flush() 

858 

859 # Get project issues boards 

860 if options.get_project_issues_boards: 

861 issues_boards_objects = gitlab.project_get_issues_boards(criteria) 

862 if issues_boards_objects: 

863 print(f'{Colors.BOLD} - Get issues boards to JSON: ' 

864 f'{Colors.RESET}', end='') 

865 issues_boards_list: List[Dict[str, bool | str | List[Dict[str, str]] 

866 | None]] = [] 

867 for board in issues_boards_objects: 

868 board_lists: List[Dict[str, str]] = [] 

869 for board_list in board.lists.list(get_all=True): 

870 board_lists += [{ 

871 'label': board_list.label['name'], 

872 'position': board_list.position, 

873 }] 

874 issues_boards_list += [{ 

875 'name': board.name, 

876 'hide_backlog_list': board.hide_backlog_list, 

877 'hide_closed_list': board.hide_closed_list, 

878 'lists': board_lists, 

879 }] 

880 print(json.dumps(issues_boards_list)) 

881 Platform.flush() 

882 

883 # Set project issues boards 

884 elif options.set_project_issues_boards: 

885 print(f'{Colors.BOLD} - Set issues boards from JSON: ' 

886 f'{Colors.RESET}', end='') 

887 if not project.archived: 

888 issues_boards_names: List[str] = [] 

889 issues_boards_json = json.loads(options.set_project_issues_boards) 

890 for board_json in issues_boards_json: 

891 board_set = gitlab.project_set_issues_board( 

892 criteria, name=board_json['name'], 

893 hide_backlog_list=board_json['hide_backlog_list'], 

894 hide_closed_list=board_json['hide_closed_list'], lists=[{ 

895 'label': board_list['label'], 

896 'position': board_list['position'], 

897 } for board_list in board_json['lists']]) 

898 issues_boards_names += [board_set.name] 

899 print(f'{Colors.CYAN}{", ".join(issues_boards_names)}' 

900 f'{Colors.RESET}') 

901 else: 

902 print(f'{Colors.RED}Archived project are read-only' 

903 f'{Colors.RESET}') 

904 Platform.flush() 

905 

906 # Get project labels 

907 if options.get_project_labels: 

908 labels_objects = gitlab.project_get_labels(criteria) 

909 if labels_objects: 

910 print(f'{Colors.BOLD} - Get labels to JSON: ' 

911 f'{Colors.RESET}', end='') 

912 labels_list: List[Dict[str, str | None]] = [] 

913 for label in labels_objects: 

914 labels_list += [{ 

915 'name': label.name, 

916 'description': label.description, 

917 'text_color': label.text_color, 

918 'color': label.color, 

919 'priority': label.priority, 

920 }] 

921 print(json.dumps(labels_list)) 

922 Platform.flush() 

923 

924 # Set project labels 

925 elif options.set_project_labels: 

926 print(f'{Colors.BOLD} - Set labels from JSON: ' 

927 f'{Colors.RESET}', end='') 

928 if not project.archived: 

929 labels_names: List[str] = [] 

930 labels_json = json.loads(options.set_project_labels) 

931 for label_json in labels_json: 

932 label_set = gitlab.project_set_label( 

933 criteria, 

934 name=label_json['name'], 

935 description=label_json['description'], 

936 text_color=label_json['text_color'], 

937 color=label_json['color'], 

938 priority=label_json['priority'], 

939 ) 

940 labels_names += [label_set.name] 

941 print(f'{Colors.CYAN}{", ".join(labels_names)}' 

942 f'{Colors.RESET}') 

943 else: 

944 print(f'{Colors.RED}Archived project are read-only' 

945 f'{Colors.RESET}') 

946 Platform.flush() 

947 

948 # Dump project object 

949 if options.dump: 

950 print(' ') 

951 print(project.to_json()) 

952 

953 # Footer 

954 print(' ') 

955 Platform.flush() 

956 

957 # Result 

958 return Entrypoint.Result.SUCCESS