Coverage for src/types/gitlab.py: 65.79%

152 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 typing import Dict, List, NamedTuple 

5 

6# Modules libraries 

7from gitlab import Gitlab 

8from gitlab.exceptions import GitlabGetError, GitlabHttpError, GitlabListError 

9from gitlab.v4.objects import Project 

10 

11# AccessLevels class, pylint: disable=too-few-public-methods 

12class AccessLevels: 

13 DISABLED: str = 'disabled' 

14 PRIVATE: str = 'private' 

15 ENABLED: str = 'enabled' 

16 

17# MergeRequestsMethod class 

18class MergeRequestsMethod: 

19 ATTRIBUTE: str = 'merge_method' 

20 DEFAULT: str = 'Fast-forward' 

21 VALUES: Dict[str, str] = { 

22 'Merge': 'merge', 

23 'Semi-linear': 'rebase_merge', 

24 'Fast-forward': 'ff', 

25 } 

26 

27# MergeRequestsPipelines class 

28class MergeRequestsPipelines: 

29 ATTRIBUTE: str = 'only_allow_merge_if_pipeline_succeeds' 

30 DEFAULT: bool = True 

31 

32# MergeRequestsResolved class 

33class MergeRequestsResolved: 

34 ATTRIBUTE: str = 'only_allow_merge_if_all_discussions_are_resolved' 

35 DEFAULT: bool = True 

36 

37# MergeRequestsSquash class 

38class MergeRequestsSquash: 

39 ATTRIBUTE: str = 'squash_option' 

40 DEFAULT: str = 'Allow' 

41 VALUES: Dict[str, str] = { 

42 'Do not allow': 'never', 

43 'Allow': 'default_off', 

44 'Encourage': 'default_on', 

45 'Require': 'default_on', 

46 } 

47 

48# MergeRequestsSkipped class 

49class MergeRequestsSkipped: 

50 ATTRIBUTE: str = 'allow_merge_on_skipped_pipeline' 

51 DEFAULT: bool = True 

52 

53# ProtectionLevels class 

54class ProtectionLevels: 

55 NO_ONE: str = 'no-one' 

56 ADMINS: str = 'admins' 

57 MAINTAINERS: str = 'maintainers' 

58 DEVELOPERS: str = 'developers' 

59 

60 # Default 

61 @staticmethod 

62 def default() -> str: 

63 return ProtectionLevels.NO_ONE 

64 

65 # Names 

66 @staticmethod 

67 def names() -> List[str]: 

68 return [ 

69 ProtectionLevels.NO_ONE, 

70 ProtectionLevels.ADMINS, 

71 ProtectionLevels.MAINTAINERS, 

72 ProtectionLevels.DEVELOPERS, 

73 ] 

74 

75# RolesCreateProjects class 

76class RolesCreateProjects: 

77 NO_ONE: str = 'noone' 

78 OWNER: str = 'owner' 

79 MAINTAINER: str = 'maintainer' 

80 DEVELOPER: str = 'developer' 

81 ADMINISTRATOR: str = 'administrator' 

82 

83 # Default 

84 @staticmethod 

85 def default() -> str: 

86 return RolesCreateProjects.DEVELOPER 

87 

88 # Names 

89 @staticmethod 

90 def names() -> List[str]: 

91 return [ 

92 RolesCreateProjects.NO_ONE, 

93 RolesCreateProjects.OWNER, 

94 RolesCreateProjects.MAINTAINER, 

95 RolesCreateProjects.DEVELOPER, 

96 RolesCreateProjects.ADMINISTRATOR, 

97 ] 

98 

99# RolesCreateSubgroups class 

100class RolesCreateSubgroups: 

101 OWNER: str = 'owner' 

102 MAINTAINER: str = 'maintainer' 

103 

104 # Default 

105 @staticmethod 

106 def default() -> str: 

107 return RolesCreateSubgroups.MAINTAINER 

108 

109 # Names 

110 @staticmethod 

111 def names() -> List[str]: 

112 return [ 

113 RolesCreateSubgroups.OWNER, 

114 RolesCreateSubgroups.MAINTAINER, 

115 ] 

116 

117# Visibility class, pylint: disable=too-few-public-methods 

118class Visibility: 

119 PRIVATE: str = 'private' 

120 INTERNAL: str = 'internal' 

121 PUBLIC: str = 'public' 

122 

123# ProjectFeatureLevel class, pylint: disable=too-few-public-methods 

124class ProjectFeatureLevel(NamedTuple): 

125 key: str 

126 settings: Dict[str, str] = { 

127 Visibility.PRIVATE: AccessLevels.PRIVATE, 

128 Visibility.INTERNAL: AccessLevels.ENABLED, 

129 Visibility.PUBLIC: AccessLevels.ENABLED, 

130 } 

131 

132# ProjectFeatures class, pylint: disable=too-few-public-methods 

133class ProjectFeatures: 

134 

135 # Members 

136 cache_keys: List[str] = [] 

137 cache_names: List[str] = [] 

138 

139 # Feature 

140 class Feature(NamedTuple): 

141 

142 # Variables 

143 name: str 

144 access_level: List[ProjectFeatureLevel] = [] 

145 enabled: List[str] = [] 

146 tests: List[str] = [] 

147 

148 # Get 

149 @staticmethod 

150 def get(key: str) -> Feature: 

151 

152 # Get feature object 

153 feature = getattr(ProjectFeatures, key) 

154 assert isinstance(feature, ProjectFeatures.Feature) 

155 return feature 

156 

157 # Keys 

158 @staticmethod 

159 def keys() -> List[str]: 

160 

161 # Evaluate keys 

162 if ProjectFeatures.cache_keys: 

163 return ProjectFeatures.cache_keys 

164 

165 # Evaluate keys 

166 ProjectFeatures.cache_keys = [ 

167 key for key in ProjectFeatures.__dict__ 

168 if isinstance(getattr(ProjectFeatures, key), ProjectFeatures.Feature) 

169 ] 

170 return ProjectFeatures.cache_keys 

171 

172 # Names 

173 @staticmethod 

174 def names() -> List[str]: 

175 

176 # Evaluate names 

177 if ProjectFeatures.cache_names: 

178 return ProjectFeatures.cache_names 

179 

180 # Evaluate names 

181 ProjectFeatures.cache_names = [ 

182 ProjectFeatures.get(key).name for key in ProjectFeatures.__dict__ 

183 if isinstance(getattr(ProjectFeatures, key), ProjectFeatures.Feature) 

184 ] 

185 return ProjectFeatures.cache_names 

186 

187 # Project test feature, pylint: disable=too-many-branches 

188 @staticmethod 

189 def test( 

190 gitlab: Gitlab, 

191 project: Project, 

192 tests: List[str], 

193 ) -> bool: 

194 

195 # Avoid without tests 

196 if not tests: 

197 return False 

198 

199 # Iterate through tests 

200 try: 

201 for test in tests: 

202 

203 # Handle CI/CD test 

204 if test == 'CI_CD': 

205 test_ci_jobs = project.jobs.list(get_all=False) 

206 test_ci_files = any( 

207 'name' in item and item['name'] == '.gitlab-ci.yml' 

208 for item in project.repository_tree(get_all=True) \ 

209 ) 

210 test_ci_token_access: bool = False 

211 try: 

212 test_ci_token_access = any( 

213 'path_with_namespace' in item \ 

214 and isinstance(item, dict) \ 

215 and item['path_with_namespace'] != project.path_with_namespace \ 

216 for item in gitlab.http_get( 

217 f'/projects/{project.id}/job_token_scope/allowlist', 

218 get_all=True, 

219 ) \ 

220 ) 

221 except GitlabHttpError: 

222 test_ci_token_access = False 

223 assert (test_ci_jobs or test_ci_files or test_ci_token_access) 

224 

225 # Handle commits test 

226 elif test == 'COMMITS': 

227 assert project.commits.list(get_all=False) 

228 

229 # Handle environments test 

230 elif test == 'ENVIRONMENTS': 

231 assert project.environments.list(get_all=False) 

232 

233 # Handle forks test 

234 elif test == 'FORKS': 

235 assert project.forks.list(get_all=False) 

236 

237 # Handle Git LFS test 

238 elif test == 'GIT_LFS': 

239 assert project.statistics['lfs_objects_size'] > 0 

240 

241 # Handle issues test 

242 elif test == 'ISSUES': 

243 assert project.issues.list(get_all=False) 

244 

245 # Handle merge requests test 

246 elif test == 'MERGE_REQUESTS': 

247 assert (project.mergerequests.list(get_all=False) 

248 or len(project.branches.list(get_all=False)) > 1) 

249 

250 # Handle packages test 

251 elif test == 'PACKAGES': 

252 assert project.packages.list(get_all=False) 

253 

254 # Handle pages test 

255 elif test == 'PAGES': 

256 test_pages_jobs = any( 

257 job.name == 'pages' for job in project.jobs.list(get_all=True)) 

258 assert test_pages_jobs 

259 

260 # Handle releases test 

261 elif test == 'RELEASES': 

262 assert project.releases.list(get_all=False) 

263 

264 # Handle repositories test 

265 elif test == 'REPOSITORIES': 

266 assert project.repositories.list(get_all=False) 

267 

268 # Handle snippets test 

269 elif test == 'SNIPPETS': 

270 assert project.snippets.list(get_all=False) 

271 

272 # Handle wiki test 

273 elif test == 'WIKI': 

274 assert project.wikis.list(get_all=False) 

275 

276 # Handle failed test 

277 except (AssertionError, GitlabGetError, GitlabListError): 

278 return False 

279 

280 # Result 

281 return True 

282 

283 # Issues 

284 ISSUES = Feature( 

285 name='Issues', 

286 access_level=[ 

287 ProjectFeatureLevel('issues_access_level'), 

288 ], 

289 enabled=[ 

290 'issues_enabled', 

291 ], 

292 tests=[ 

293 'ISSUES', 

294 ], 

295 ) 

296 

297 # Merge requests 

298 MERGE_REQUESTS = Feature( 

299 name='Merge requests', 

300 access_level=[ 

301 ProjectFeatureLevel('merge_requests_access_level'), 

302 ], 

303 enabled=[ 

304 'merge_requests_enabled', 

305 ], 

306 tests=[ 

307 'MERGE_REQUESTS', 

308 ], 

309 ) 

310 

311 # Forks 

312 FORKS = Feature( 

313 name='Forks', 

314 access_level=[ 

315 ProjectFeatureLevel('forking_access_level'), 

316 ], 

317 tests=[ 

318 'FORKS', 

319 ], 

320 ) 

321 

322 # Git LFS 

323 GIT_LFS = Feature( 

324 name='Git LFS', 

325 enabled=[ 

326 'lfs_enabled', 

327 ], 

328 tests=[ 

329 'GIT_LFS', 

330 ], 

331 ) 

332 

333 # CI/CD 

334 CI_CD = Feature( 

335 name='CI/CD', 

336 access_level=[ 

337 ProjectFeatureLevel('builds_access_level'), 

338 ], 

339 enabled=[ 

340 'jobs_enabled', 

341 ], 

342 tests=[ 

343 'CI_CD', 

344 ], 

345 ) 

346 

347 # Repository 

348 REPOSITORY = Feature( 

349 name='Repository', 

350 access_level=[ 

351 ProjectFeatureLevel( 

352 'repository_access_level', 

353 { 

354 Visibility.PRIVATE: AccessLevels.ENABLED, 

355 Visibility.INTERNAL: AccessLevels.ENABLED, 

356 Visibility.PUBLIC: AccessLevels.ENABLED, 

357 }, 

358 ), 

359 ProjectFeatureLevel('merge_requests_access_level'), 

360 ProjectFeatureLevel('forking_access_level'), 

361 ProjectFeatureLevel('builds_access_level'), 

362 ], 

363 enabled=[ 

364 'lfs_enabled', 

365 'jobs_enabled', 

366 ], 

367 tests=[ 

368 'COMMITS', 

369 ], 

370 ) 

371 

372 # Container registry 

373 CONTAINER_REGISTRY = Feature( 

374 name='Container registry', 

375 enabled=[ 

376 'container_registry_enabled', 

377 ], 

378 access_level=[ 

379 ProjectFeatureLevel('container_registry_access_level'), 

380 ], 

381 tests=[ 

382 'REPOSITORIES', 

383 ], 

384 ) 

385 

386 # Analytics 

387 ANALYTICS = Feature( 

388 name='Analytics', 

389 access_level=[ 

390 ProjectFeatureLevel('analytics_access_level'), 

391 ], 

392 tests=[], 

393 ) 

394 

395 # Security and Compliance 

396 SECURITY_AND_COMPLIANCE = Feature( 

397 name='Security and Compliance', 

398 access_level=[ 

399 ProjectFeatureLevel( 

400 'security_and_compliance_access_level', { 

401 Visibility.PRIVATE: AccessLevels.PRIVATE, 

402 Visibility.INTERNAL: AccessLevels.PRIVATE, 

403 Visibility.PUBLIC: AccessLevels.PRIVATE, 

404 }), 

405 ], 

406 tests=[], 

407 ) 

408 

409 # Wiki 

410 WIKI = Feature( 

411 name='Wiki', 

412 enabled=[ 

413 'wiki_enabled', 

414 ], 

415 access_level=[ 

416 ProjectFeatureLevel('wiki_access_level'), 

417 ], 

418 tests=[ 

419 'WIKI', 

420 ], 

421 ) 

422 

423 # Snippets 

424 SNIPPETS = Feature( 

425 name='Snippets', 

426 enabled=[ 

427 'snippets_enabled', 

428 ], 

429 access_level=[ 

430 ProjectFeatureLevel('snippets_access_level'), 

431 ], 

432 tests=[ 

433 'SNIPPETS', 

434 ], 

435 ) 

436 

437 # Package registry 

438 PACKAGE_REGISTRY = Feature( 

439 name='Package registry', 

440 enabled=[ 

441 'packages_enabled', 

442 ], 

443 tests=[ 

444 'PACKAGES', 

445 ], 

446 ) 

447 

448 # Model experiments 

449 MODEL_EXPERIMENTS = Feature( 

450 name='Model experiments', 

451 access_level=[ 

452 ProjectFeatureLevel('model_experiments_access_level'), 

453 ], 

454 tests=[], 

455 ) 

456 

457 # Model registry 

458 MODEL_REGISTRY = Feature( 

459 name='Model registry', 

460 access_level=[ 

461 ProjectFeatureLevel('model_registry_access_level'), 

462 ], 

463 tests=[], 

464 ) 

465 

466 # Pages 

467 PAGES = Feature( 

468 name='Pages', 

469 access_level=[ 

470 ProjectFeatureLevel( 

471 'pages_access_level', { 

472 Visibility.PRIVATE: AccessLevels.PRIVATE, 

473 Visibility.INTERNAL: AccessLevels.PRIVATE, 

474 Visibility.PUBLIC: AccessLevels.ENABLED, 

475 }) 

476 ], 

477 tests=[ 

478 'PAGES', 

479 ], 

480 ) 

481 

482 # Monitor 

483 MONITOR = Feature( 

484 name='Monitor', 

485 access_level=[ 

486 ProjectFeatureLevel('monitor_access_level'), 

487 ], 

488 tests=[], 

489 ) 

490 

491 # Environments 

492 ENVIRONMENTS = Feature( 

493 name='Environments', 

494 access_level=[ 

495 ProjectFeatureLevel('environments_access_level'), 

496 ], 

497 tests=[ 

498 'ENVIRONMENTS', 

499 ], 

500 ) 

501 

502 # Feature flags 

503 FEATURE_FLAGS = Feature( 

504 name='Feature flags', 

505 access_level=[ 

506 ProjectFeatureLevel('feature_flags_access_level'), 

507 ], 

508 tests=[], 

509 ) 

510 

511 # Infrastructure 

512 INFRASTRUCTURE = Feature( 

513 name='Infrastructure', 

514 access_level=[ 

515 ProjectFeatureLevel('infrastructure_access_level'), 

516 ], 

517 tests=[], 

518 ) 

519 

520 # Releases 

521 RELEASES = Feature( 

522 name='Releases', 

523 access_level=[ 

524 ProjectFeatureLevel('releases_access_level'), 

525 ], 

526 tests=[ 

527 'RELEASES', 

528 ], 

529 ) 

530 

531 # Service Desk 

532 SERVICE_DESK = Feature( 

533 name='Service Desk', 

534 enabled=[ 

535 'service_desk_enabled', 

536 ], 

537 tests=[], 

538 ) 

539 

540 # Auto DevOps 

541 AUTO_DEVOPS = Feature( 

542 name='Auto DevOps', 

543 enabled=[ 

544 'auto_devops_enabled', 

545 ], 

546 tests=[], 

547 )