1- import { adminGroupPath } from '@cpn-console/shared'
2- import type { Project , StepCall } from '@cpn-console/hooks'
3- import { generateProjectKey , parseError } from '@cpn-console/hooks'
1+ import type { AdminRole , Project , StepCall } from '@cpn-console/hooks'
2+ import { generateProjectKey , parseError , specificallyEnabled } from '@cpn-console/hooks'
43import type { VaultProjectApi } from '@cpn-console/vault-plugin/types/vault-project-api.js'
5- import { ensureGroupExists , findGroupByName } from './group.js'
4+ import { addUserToGroup , ensureGroupExists , getGroupMembers , removeUserFromGroup } from './group.js'
65import type { VaultSonarSecret } from './tech.js'
76import { getAxiosInstance } from './tech.js'
8- import type { SonarUser } from './user.js'
9- import { ensureUserExists } from './user.js'
10- import type { SonarPaging } from './project.js'
7+ import { ensureUserExists , getUser } from './user.js'
118import { createDsoRepository , deleteDsoRepository , ensureRepositoryConfiguration , files , findSonarProjectsForDsoProjects } from './project.js'
12-
13- const globalPermissions = [
14- 'admin' ,
15- 'profileadmin' ,
16- 'gateadmin' ,
17- 'scan' ,
18- 'provisioning' ,
19- ]
9+ import { DEFAULT_ADMIN_GROUP_PATH , DEFAULT_READONLY_GROUP_PATH } from './infos.js'
2010
2111const projectPermissions = [
2212 'admin' ,
@@ -25,75 +15,163 @@ const projectPermissions = [
2515 'securityhotspotadmin' ,
2616 'scan' ,
2717 'user' ,
28- ]
18+ ] as const
2919
30- export async function initSonar ( ) {
31- await setTemplatePermisions ( )
32- await createAdminGroup ( )
33- await setAdminPermisions ( )
34- }
20+ const readonlyProjectPermissions = [
21+ 'codeviewer' ,
22+ 'user' ,
23+ 'scan' ,
24+ 'issueadmin' ,
25+ 'securityhotspotadmin' ,
26+ ] as const
3527
36- async function createAdminGroup ( ) {
37- const axiosInstance = getAxiosInstance ( )
38- const adminGroup = await findGroupByName ( adminGroupPath )
39- if ( ! adminGroup ) {
40- await axiosInstance ( {
41- method : 'post' ,
42- params : {
43- name : adminGroupPath ,
44- description : 'DSO platform admins' ,
28+ export const upsertAdminRole : StepCall < AdminRole > = async ( payload ) => {
29+ try {
30+ const role = payload . args
31+ const adminGroupPath = payload . config . sonarqube ?. adminGroupPath ?? DEFAULT_ADMIN_GROUP_PATH
32+ const readonlyGroupPath = payload . config . sonarqube ?. readonlyGroupPath ?? DEFAULT_READONLY_GROUP_PATH
33+ if ( ! readonlyGroupPath ) {
34+ throw new Error ( 'readonlyGroupPath is required' )
35+ }
36+ const purge = payload . config . sonarqube ?. purge
37+ if ( ! adminGroupPath ) {
38+ throw new Error ( 'adminGroupPath is required' )
39+ }
40+
41+ let managedGroupPath : string | undefined
42+
43+ if ( role . oidcGroup === adminGroupPath ) {
44+ managedGroupPath = adminGroupPath
45+ await ensureAdminTemplateExists ( adminGroupPath )
46+ await ensureGroupExists ( adminGroupPath )
47+ await setTemplateGroupPermissions ( adminGroupPath , projectPermissions , adminGroupPath )
48+ } else if ( role . oidcGroup === readonlyGroupPath ) {
49+ managedGroupPath = readonlyGroupPath
50+ await ensureReadonlyTemplateExists ( readonlyGroupPath )
51+ await ensureGroupExists ( readonlyGroupPath )
52+ await setTemplateGroupPermissions ( readonlyGroupPath , readonlyProjectPermissions , readonlyGroupPath )
53+ }
54+
55+ if ( ! managedGroupPath ) {
56+ return {
57+ status : {
58+ result : 'OK' ,
59+ message : 'Not a managed role for SonarQube plugin' ,
60+ } ,
61+ }
62+ }
63+
64+ const groupMembers = await getGroupMembers ( managedGroupPath )
65+
66+ await Promise . all ( [
67+ ...role . members . map ( ( member ) => {
68+ if ( ! groupMembers . includes ( member . email ) ) {
69+ return addUserToGroup ( managedGroupPath , member . email )
70+ . catch ( ( error ) => {
71+ console . warn ( `Failed to add user ${ member . email } to group ${ managedGroupPath } ` , error )
72+ } )
73+ }
74+ return undefined
75+ } ) ,
76+ ...groupMembers . map ( ( memberEmail ) => {
77+ if ( ! role . members . some ( m => m . email === memberEmail ) ) {
78+ if ( specificallyEnabled ( purge ) ) {
79+ return removeUserFromGroup ( managedGroupPath , memberEmail )
80+ . catch ( ( error ) => {
81+ console . warn ( `Failed to remove user ${ memberEmail } from group ${ managedGroupPath } ` , error )
82+ } )
83+ }
84+ }
85+ return undefined
86+ } ) ,
87+ ] )
88+
89+ return {
90+ status : {
91+ result : 'OK' ,
92+ message : 'Admin role synced' ,
4593 } ,
46- url : 'user_groups/create' ,
47- } )
94+ }
95+ } catch ( error ) {
96+ return {
97+ error : parseError ( error ) ,
98+ status : {
99+ result : 'KO' ,
100+ message : 'An error occured while syncing admin role' ,
101+ } ,
102+ }
48103 }
49104}
50105
51- async function setAdminPermisions ( ) {
106+ async function setTemplateGroupPermissions ( groupName : string , permissions : readonly string [ ] , templateName : string ) {
52107 const axiosInstance = getAxiosInstance ( )
53- for ( const permission of globalPermissions ) {
54- await axiosInstance ( {
108+ await Promise . all ( permissions . map ( permission =>
109+ axiosInstance ( {
55110 method : 'post' ,
56111 params : {
57- groupName : adminGroupPath ,
112+ groupName,
113+ templateName,
58114 permission,
59115 } ,
60- url : 'permissions/add_group ' ,
61- } )
62- }
116+ url : 'permissions/add_group_to_template ' ,
117+ } ) ,
118+ ) )
63119}
64120
65- async function setTemplatePermisions ( ) {
121+ async function ensureAdminTemplateExists ( adminTemplateName : string ) {
66122 const axiosInstance = getAxiosInstance ( )
123+
124+ // Create Admin Template
67125 await axiosInstance ( {
68126 method : 'post' ,
69- params : { name : 'Forge Default' } ,
127+ params : { name : adminTemplateName } ,
70128 url : 'permissions/create_template' ,
71129 validateStatus : code => [ 200 , 400 ] . includes ( code ) ,
72130 } )
73- for ( const permission of projectPermissions ) {
74- await axiosInstance ( {
131+
132+ // Add Project Creator and sonar-administrators to Admin Template
133+ await Promise . all ( projectPermissions . map ( permission =>
134+ axiosInstance ( {
75135 method : 'post' ,
76136 params : {
77- templateName : 'Forge Default' ,
137+ templateName : adminTemplateName ,
78138 permission,
79139 } ,
80140 url : 'permissions/add_project_creator_to_template' ,
81- } )
82- await axiosInstance ( {
141+ } ) ,
142+ ) )
143+ await setTemplateGroupPermissions ( 'sonar-administrators' , projectPermissions , adminTemplateName )
144+ }
145+
146+ async function ensureReadonlyTemplateExists ( readonlyTemplateName : string ) {
147+ const axiosInstance = getAxiosInstance ( )
148+
149+ // Create Readonly Template
150+ await axiosInstance ( {
151+ method : 'post' ,
152+ params : { name : readonlyTemplateName } ,
153+ url : 'permissions/create_template' ,
154+ validateStatus : code => [ 200 , 400 ] . includes ( code ) ,
155+ } )
83156
157+ // Add Project Creator and sonar-administrators to Readonly Template
158+ await Promise . all ( projectPermissions . map ( permission =>
159+ axiosInstance ( {
84160 method : 'post' ,
85161 params : {
86- groupName : 'sonar-administrators' ,
87- templateName : 'Forge Default' ,
162+ templateName : readonlyTemplateName ,
88163 permission,
89164 } ,
90- url : 'permissions/add_group_to_template' ,
91- } )
92- }
165+ url : 'permissions/add_project_creator_to_template' ,
166+ } ) ,
167+ ) )
168+ await setTemplateGroupPermissions ( 'sonar-administrators' , projectPermissions , readonlyTemplateName )
169+
170+ // Set Readonly Template as Default
93171 await axiosInstance ( {
94172 method : 'post' ,
95173 params : {
96- templateName : 'Forge Default' ,
174+ templateName : readonlyTemplateName ,
97175 } ,
98176 url : 'permissions/set_default_template' ,
99177 } )
@@ -166,7 +244,9 @@ export const setVariables: StepCall<Project> = async (payload) => {
166244 ...project . repositories . map ( async ( repo ) => {
167245 const projectKey = generateProjectKey ( projectSlug , repo . internalRepoName )
168246 const repoId = await payload . apis . gitlab . getProjectId ( repo . internalRepoName )
169- if ( ! repoId ) return
247+ if ( ! repoId ) {
248+ throw new Error ( `Unable to find GitLab project for repository ${ repo . internalRepoName } ` )
249+ }
170250 const listVars = await gitlabApi . getGitlabRepoVariables ( repoId )
171251 return [
172252 await gitlabApi . setGitlabRepoVariable ( repoId , listVars , {
@@ -231,13 +311,7 @@ export const deleteProject: StepCall<Project> = async (payload) => {
231311 try {
232312 const sonarRepositories = await findSonarProjectsForDsoProjects ( projectSlug )
233313 await Promise . all ( sonarRepositories . map ( repo => deleteRepo ( repo . key ) ) )
234- const users : { paging : SonarPaging , users : SonarUser [ ] } = ( await axiosInstance ( {
235- url : 'users/search' ,
236- params : {
237- q : username ,
238- } ,
239- } ) ) ?. data
240- const user = users . users . find ( u => u . login === username )
314+ const user = await getUser ( username )
241315 if ( ! user ) {
242316 return {
243317 status : {
0 commit comments