// ----------------------------------------------------------------------------- // Housekeeping: Unbenutzte Berechtigungsschemata finden UND löschen // Jira Cloud - ScriptRunner Console // ----------------------------------------------------------------------------- import groovy.json.JsonOutput // --- Konfiguration ----------------------------------------------------------- // đŸ”„ Wenn true -> nur Testlauf, nichts wird gelöscht. // đŸ”„ Wenn false -> unbenutzte Schemas (ohne EXCLUDED_IDS) werden gelöscht. final boolean DRY_RUN = true // IDs, die niemals gelöscht werden sollen (z. B. Default/System-Schemata) final Set EXCLUDED_IDS = [0] as Set // bei Bedarf ergĂ€nzen, z.B. 10000L etc. // --- Schritt 1: Alle Berechtigungsschemata holen ---------------------------- def schemesResp = get("/rest/api/3/permissionscheme").asObject(Map) if (schemesResp.status != 200) { logger.error("Konnte Berechtigungsschemata nicht laden: ${schemesResp.status} - ${schemesResp.body}") return "Fehler beim Laden der Berechtigungsschemata. Siehe Log." } def schemes = schemesResp.body?.permissionSchemes ?: [] logger.info "Anzahl Berechtigungsschemata insgesamt: ${schemes.size()}" // Map: schemeId -> [scheme: , used: boolean, projects: [keys]] def schemeUsage = schemes.collectEntries { scheme -> def id = (scheme.id ?: scheme["id"]) as Long [ (id): [ scheme : scheme, used : false, projects: [] ] ] } // --- Schritt 2: Alle Projekte holen & Permission-Scheme je Projekt ermitteln -- int startAt = 0 int maxResults = 50 int totalProjects = 0 boolean finished = false while (!finished) { def projResp = get("/rest/api/3/project/search?startAt=${startAt}&maxResults=${maxResults}") .asObject(Map) if (projResp.status != 200) { logger.error("Konnte Projekte nicht laden (startAt=${startAt}): ${projResp.status} - ${projResp.body}") break } def body = projResp.body ?: [:] def projects = body.values ?: [] totalProjects += projects.size() projects.each { proj -> def projectKey = proj.key def projectId = proj.id def permResp = get("/rest/api/3/project/${projectId}/permissionscheme") .asObject(Map) if (permResp.status == 200) { def schemeId = permResp.body?.id if (schemeId) { def idLong = (schemeId as Long) def entry = schemeUsage[idLong] if (entry) { entry.used = true entry.projects << projectKey } else { logger.warn "Projekt ${projectKey} nutzt Berechtigungsschema ${schemeId}, das nicht in der globalen Liste war." } } } else if (permResp.status == 404) { // Team-managed-Projekte -> haben kein klassisches Permission Scheme } else { logger.warn "Konnte Permission Scheme fĂŒr Projekt ${projectKey} nicht laden: ${permResp.status}" } } int total = (body.total ?: totalProjects) as int startAt += maxResults if (startAt >= total) { finished = true } } // --- Schritt 3: Unbenutzte (und nicht ausgeschlossene) Schemata bestimmen --- def unused = schemeUsage.values() .findAll { entry -> def id = (entry.scheme.id ?: 0L) as Long !entry.used && !EXCLUDED_IDS.contains(id) } .sort { it.scheme.name?.toString()?.toLowerCase() } logger.info "Projekte insgesamt : ${totalProjects}" logger.info "Ausgeschlossene Schema-IDs : ${EXCLUDED_IDS.join(', ')}" logger.info "Unbenutzte Schemata (Kandidaten): ${unused.size()}" // --- Schritt 4: Optional löschen -------------------------------------------- def deleted = [] def failed = [] if (!DRY_RUN) { unused.each { entry -> def s = entry.scheme def id = (s.id as Long) logger.info "Lösche Berechtigungsschema ID=${id}, Name=\"${s.name}\" ..." def delResp = delete("/rest/api/3/permissionscheme/${id}") .asString() if (delResp.status in [200, 204]) { logger.info "Erfolgreich gelöscht: ID=${id}, Name=\"${s.name}\"" deleted << [ id : id, name : s.name, description: s.description ] } else { logger.warn "Löschen fehlgeschlagen fĂŒr ID=${id}, Name=\"${s.name}\": Status=${delResp.status}, Body=${delResp.body}" failed << [ id : id, name : s.name, status : delResp.status, body : delResp.body ] } } } else { logger.info "DRY_RUN = true -> Es wird nichts gelöscht, nur Kandidaten ermittelt." } // --- Schritt 5: Zusammenfassung zurĂŒckgeben --------------------------------- def lines = [] lines << "=== Berechtigungsschemata Housekeeping ===" lines << "DRY_RUN : ${DRY_RUN}" lines << "Gesamt-Schemata : ${schemes.size()}" lines << "Gesamt-Projekte : ${totalProjects}" lines << "Ausgeschlossene IDs : ${EXCLUDED_IDS.join(', ')}" lines << "Kandidaten (unused) : ${unused.size()}" if (!DRY_RUN) { lines << "Gelöscht : ${deleted.size()}" lines << "Fehlgeschlagen : ${failed.size()}" } lines << "" lines << "Kandidaten (unbenutzte Schemas, exkl. EXCLUDED_IDS):" unused.each { entry -> def s = entry.scheme lines << String.format( "- ID=%s | Name=\"%s\" | Beschreibung=\"%s\" | Projekte=%s", s.id, s.name ?: "", (s.description ?: "").replaceAll('\\s+', ' ').trim(), entry.projects ?: [] ) } def result = [ summary : [ dryRun : DRY_RUN, totalSchemes : schemes.size(), totalProjects : totalProjects, excludedIDs : EXCLUDED_IDS, candidateUnused : unused.size(), deleted : deleted.size(), failed : failed.size() ], deletedPermissionSchemes: deleted, failedDeletions : failed, candidateUnusedSchemes : unused.collect { e -> def s = e.scheme [ id : s.id, name : s.name, description : s.description, projectsUsing: e.projects ] } ] logger.info lines.join("\n") return lines.join("\n") + "\n\nJSON:\n" + JsonOutput.prettyPrint(JsonOutput.toJson(result))