|
1 | | -import { okStatus, parseError, specificallyDisabled } from '@cpn-console/hooks' |
2 | | -import type { ClusterObject, PluginResult, Project, ProjectLite, StepCall, UniqueRepo, ZoneObject } from '@cpn-console/hooks' |
| 1 | +import { okStatus, parseError, specificallyDisabled, specificallyEnabled } from '@cpn-console/hooks' |
| 2 | +import type { AdminRole, ClusterObject, PluginResult, Project, ProjectLite, StepCall, UniqueRepo, ZoneObject, ProjectMember } from '@cpn-console/hooks' |
3 | 3 | import { insert } from '@cpn-console/shared' |
| 4 | +import { AccessLevel } from '@gitbeaker/core' |
4 | 5 | import { deleteGroup } from './group.js' |
5 | | -import { createUsername, getUser } from './user.js' |
| 6 | +import { createUsername, getUser, upsertUser } from './user.js' |
6 | 7 | import { ensureMembers } from './members.js' |
7 | 8 | import { ensureRepositories } from './repositories.js' |
8 | 9 | import type { VaultSecrets } from './utils.js' |
9 | | -import { cleanGitlabError } from './utils.js' |
10 | 10 | import config from './config.js' |
| 11 | +import type { GitlabProjectApi } from './class.js' |
| 12 | +import { cleanGitlabError, matchRole } from './utils.js' |
| 13 | +import { |
| 14 | + DEFAULT_ADMIN_GROUP_PATH, |
| 15 | + DEFAULT_AUDITOR_GROUP_PATH, |
| 16 | + DEFAULT_PROJECT_DEVELOPER_GROUP_PATH_SUFFIX, |
| 17 | + DEFAULT_PROJECT_MAINTAINER_GROUP_PATH_SUFFIX, |
| 18 | + DEFAULT_PROJECT_REPORTER_GROUP_PATH_SUFFIX, |
| 19 | +} from './infos.js' |
11 | 20 |
|
12 | 21 | // Check |
13 | 22 | export const checkApi: StepCall<Project> = async (payload) => { |
@@ -232,3 +241,187 @@ export const commitFiles: StepCall<UniqueRepo | Project | ClusterObject | ZoneOb |
232 | 241 | return returnResult |
233 | 242 | } |
234 | 243 | } |
| 244 | + |
| 245 | +export const upsertAdminRole: StepCall<AdminRole> = async (payload) => { |
| 246 | + try { |
| 247 | + const role = payload.args |
| 248 | + const adminGroupPath = payload.config.gitlab?.adminGroupPath ?? DEFAULT_ADMIN_GROUP_PATH |
| 249 | + const auditorGroupPath = payload.config.gitlab?.auditorGroupPath ?? DEFAULT_AUDITOR_GROUP_PATH |
| 250 | + |
| 251 | + const isAdmin = role.oidcGroup === adminGroupPath ? true : undefined |
| 252 | + const isAuditor = role.oidcGroup === auditorGroupPath ? true : undefined |
| 253 | + |
| 254 | + if (isAdmin === undefined && isAuditor === undefined) { |
| 255 | + return { |
| 256 | + status: { |
| 257 | + result: 'OK', |
| 258 | + message: 'Not a managed role for GitLab plugin', |
| 259 | + }, |
| 260 | + } |
| 261 | + } |
| 262 | + |
| 263 | + for (const member of role.members) { |
| 264 | + await upsertUser(member, isAdmin, isAuditor) |
| 265 | + } |
| 266 | + |
| 267 | + return { |
| 268 | + status: { |
| 269 | + result: 'OK', |
| 270 | + message: 'Members synced', |
| 271 | + }, |
| 272 | + } |
| 273 | + } catch (error) { |
| 274 | + return { |
| 275 | + error: parseError(cleanGitlabError(error)), |
| 276 | + status: { |
| 277 | + result: 'KO', |
| 278 | + message: 'An error occured while syncing admin role', |
| 279 | + }, |
| 280 | + } |
| 281 | + } |
| 282 | +} |
| 283 | + |
| 284 | +export const deleteAdminRole: StepCall<AdminRole> = async (payload) => { |
| 285 | + try { |
| 286 | + const role = payload.args |
| 287 | + const adminGroupPath = payload.config.gitlab?.adminGroupPath ?? DEFAULT_ADMIN_GROUP_PATH |
| 288 | + const auditorGroupPath = payload.config.gitlab?.auditorGroupPath ?? DEFAULT_AUDITOR_GROUP_PATH |
| 289 | + |
| 290 | + const isAdmin = role.oidcGroup === adminGroupPath ? false : undefined |
| 291 | + const isAuditor = role.oidcGroup === auditorGroupPath ? false : undefined |
| 292 | + |
| 293 | + if (isAdmin === undefined && isAuditor === undefined) { |
| 294 | + return { |
| 295 | + status: { |
| 296 | + result: 'OK', |
| 297 | + message: 'Not a managed role for GitLab plugin', |
| 298 | + }, |
| 299 | + } |
| 300 | + } |
| 301 | + |
| 302 | + for (const member of role.members) { |
| 303 | + await upsertUser(member, isAdmin, isAuditor) |
| 304 | + } |
| 305 | + |
| 306 | + return { |
| 307 | + status: { |
| 308 | + result: 'OK', |
| 309 | + message: 'Admin role deleted and members synced', |
| 310 | + }, |
| 311 | + } |
| 312 | + } catch (error) { |
| 313 | + return { |
| 314 | + error: parseError(cleanGitlabError(error)), |
| 315 | + status: { |
| 316 | + result: 'KO', |
| 317 | + message: 'An error occured while deleting admin role', |
| 318 | + }, |
| 319 | + } |
| 320 | + } |
| 321 | +} |
| 322 | + |
| 323 | +export const upsertProjectMember: StepCall<ProjectMember> = async (payload) => { |
| 324 | + const member = payload.args |
| 325 | + const { gitlab: gitlabApi } = payload.apis as { gitlab: GitlabProjectApi } // TODO: apis is never type for some resaon |
| 326 | + const purge = payload.config.gitlab?.purge |
| 327 | + const projectReporterGroupPathSuffix = payload.config.gitlab?.projectReporterGroupPathSuffix ?? DEFAULT_PROJECT_REPORTER_GROUP_PATH_SUFFIX |
| 328 | + const projectDeveloperGroupPathSuffix = payload.config.gitlab?.projectDeveloperGroupPathSuffix ?? DEFAULT_PROJECT_DEVELOPER_GROUP_PATH_SUFFIX |
| 329 | + const projectMaintainerGroupPathSuffix = payload.config.gitlab?.projectMaintainerGroupPathSuffix ?? DEFAULT_PROJECT_MAINTAINER_GROUP_PATH_SUFFIX |
| 330 | + |
| 331 | + try { |
| 332 | + const gitlabUser = await upsertUser({ |
| 333 | + id: member.userId, |
| 334 | + firstName: member.firstName, |
| 335 | + lastName: member.lastName, |
| 336 | + email: member.email, |
| 337 | + }) |
| 338 | + |
| 339 | + let maxAccessLevel: number | undefined |
| 340 | + |
| 341 | + if (member.project.owner.id === member.userId) { |
| 342 | + maxAccessLevel = AccessLevel.OWNER |
| 343 | + } else if (member.roles.find(role => role.oidcGroup && matchRole(member.project.slug, role.oidcGroup, projectReporterGroupPathSuffix))) { |
| 344 | + maxAccessLevel = AccessLevel.GUEST |
| 345 | + } else if (member.roles.find(role => role.oidcGroup && matchRole(member.project.slug, role.oidcGroup, projectDeveloperGroupPathSuffix))) { |
| 346 | + maxAccessLevel = AccessLevel.DEVELOPER |
| 347 | + } else if (member.roles.find(role => role.oidcGroup && matchRole(member.project.slug, role.oidcGroup, projectMaintainerGroupPathSuffix))) { |
| 348 | + maxAccessLevel = AccessLevel.MAINTAINER |
| 349 | + } |
| 350 | + |
| 351 | + const groupMembers = await gitlabApi.getGroupMembers() |
| 352 | + const existingMember = groupMembers.find(m => m.id === gitlabUser.id) |
| 353 | + |
| 354 | + if (maxAccessLevel === undefined) { |
| 355 | + if (specificallyEnabled(purge)) { |
| 356 | + if (existingMember) { |
| 357 | + await gitlabApi.removeGroupMember(gitlabUser.id) |
| 358 | + } |
| 359 | + return { |
| 360 | + status: { |
| 361 | + result: 'OK', |
| 362 | + message: 'Member has no matching roles, removed from group', |
| 363 | + }, |
| 364 | + } |
| 365 | + } else { |
| 366 | + console.warn(`Member ${gitlabUser.username} has no matching roles, not synced`) |
| 367 | + } |
| 368 | + } |
| 369 | + |
| 370 | + if (existingMember) { |
| 371 | + if (existingMember.access_level !== maxAccessLevel) { |
| 372 | + await gitlabApi.editGroupMember(gitlabUser.id, maxAccessLevel) |
| 373 | + } |
| 374 | + } else { |
| 375 | + await gitlabApi.addGroupMember(gitlabUser.id, maxAccessLevel) |
| 376 | + } |
| 377 | + |
| 378 | + return { |
| 379 | + status: { |
| 380 | + result: 'OK', |
| 381 | + message: 'Member synced', |
| 382 | + }, |
| 383 | + } |
| 384 | + } catch (error) { |
| 385 | + return { |
| 386 | + error: parseError(cleanGitlabError(error)), |
| 387 | + status: { |
| 388 | + result: 'KO', |
| 389 | + message: 'An error happened while syncing project member', |
| 390 | + }, |
| 391 | + } |
| 392 | + } |
| 393 | +} |
| 394 | + |
| 395 | +export const deleteProjectMember: StepCall<ProjectMember> = async (payload) => { |
| 396 | + const member = payload.args |
| 397 | + const { gitlab: gitlabApi } = payload.apis as { gitlab: GitlabProjectApi } // TODO: apis is never type for some resaon |
| 398 | + |
| 399 | + try { |
| 400 | + const userInfos = await getUser({ ...member, id: member.userId, username: createUsername(member.email) }) |
| 401 | + if (!userInfos) { |
| 402 | + return { |
| 403 | + status: { |
| 404 | + result: 'OK', |
| 405 | + message: 'User not found in GitLab', |
| 406 | + }, |
| 407 | + } |
| 408 | + } |
| 409 | + |
| 410 | + await gitlabApi.removeGroupMember(userInfos.id) |
| 411 | + |
| 412 | + return { |
| 413 | + status: { |
| 414 | + result: 'OK', |
| 415 | + message: 'Member deleted', |
| 416 | + }, |
| 417 | + } |
| 418 | + } catch (error) { |
| 419 | + return { |
| 420 | + error: parseError(cleanGitlabError(error)), |
| 421 | + status: { |
| 422 | + result: 'KO', |
| 423 | + message: 'An error happened while deleting project member', |
| 424 | + }, |
| 425 | + } |
| 426 | + } |
| 427 | +} |
0 commit comments