/** * Permission Scheme Housekeeping (Cloud) – Report + optional Delete * ---------------------------------------------------------------- * Delete rule: * - Scheme darf nur gelöscht werden, wenn: * 1) es in keinem PROTECTED_PROJECT verwendet wird * 2) es nicht über PROTECTED_SCHEME_IDS geschützt ist * 3) es nicht über PROTECTED_NAME_PATTERNS geschützt ist */ def PROTECTED_PROJECT_KEYS = ["NIN","NICS","NINPDS","NINPDSARC","CS","CRON"] // Zusätzliche Sicherung: def PROTECTED_SCHEME_IDS = [ // "10000", // <- optional: hier IDs eintragen, die NIE gelöscht werden dürfen ] def PROTECTED_NAME_PATTERNS = [ "default", "system" ] def DRY_RUN = true // erst auf false, wenn der Report passt logger.info("=== Permission Scheme Housekeeping ===") logger.info("Protected projects: ${PROTECTED_PROJECT_KEYS}") logger.info("Protected scheme IDs: ${PROTECTED_SCHEME_IDS}") logger.info("Protected name patterns: ${PROTECTED_NAME_PATTERNS}") logger.info("DRY_RUN: ${DRY_RUN}") // Helper: Name-Schutz (case-insensitive) def isNameProtected = { String name -> def n = (name ?: "").toLowerCase() return PROTECTED_NAME_PATTERNS.any { p -> n.contains((p ?: "").toLowerCase()) } } // Helper: ID-Schutz def isIdProtected = { String id -> return PROTECTED_SCHEME_IDS.any { it?.toString() == id?.toString() } } // 1) Project -> PermissionSchemeId (nur 6 Calls) def projectToSchemeId = [:] PROTECTED_PROJECT_KEYS.each { key -> def resp = get("/rest/api/3/project/${key}/permissionscheme") .asObject(Map) if (resp.status == 200) { // Jira liefert hier i.d.R. {id, name, ...} projectToSchemeId[key] = resp.body?.id?.toString() } else { projectToSchemeId[key] = null logger.warn("WARN|PROJECT_LOOKUP_FAILED|${key}|status=${resp.status}") } } logger.info("INFO|PROJECT_SCHEME_MAP|${projectToSchemeId}") // 2) Alle Permission Schemes holen def schemesResp = get("/rest/api/3/permissionscheme") .asObject(Map) assert schemesResp.status == 200 // Je nach API-Response ist das typischerweise body.permissionSchemes def schemes = schemesResp.body?.permissionSchemes ?: [] logger.info("INFO|TOTAL_SCHEMES|${schemes.size()}") def candidates = [] // [id, name, reason] def keptByUsage = 0 def keptByRule = 0 schemes.each { scheme -> def schemeId = scheme.id?.toString() def schemeName = scheme.name?.toString() def usedBy = projectToSchemeId.findAll { k, v -> v == schemeId }*.key def nameProtected = isNameProtected(schemeName) def idProtected = isIdProtected(schemeId) // Schutz greift immer (egal ob benutzt oder nicht) if (idProtected || nameProtected) { keptByRule++ def why = [] if (idProtected) why << "ID_PROTECTED" if (nameProtected) why << "NAME_PROTECTED" logger.info("KEEP|schemeId=${schemeId}|name=${schemeName}|usedBy=${usedBy}|reason=${why}") return } // Projekt-Usage entscheidet if (usedBy && !usedBy.isEmpty()) { keptByUsage++ logger.info("KEEP|schemeId=${schemeId}|name=${schemeName}|usedBy=${usedBy}|reason=USED_BY_PROTECTED_PROJECT") return } // Kandidat logger.info("DEL?|schemeId=${schemeId}|name=${schemeName}|usedBy=[]|reason=UNUSED") candidates << [schemeId, schemeName, "UNUSED"] if (!DRY_RUN) { def delResp = delete("/rest/api/3/permissionscheme/${schemeId}") .asString() if (delResp.status == 204) { logger.info("DEL|OK|schemeId=${schemeId}|name=${schemeName}") } else { logger.error("DEL|FAIL|schemeId=${schemeId}|name=${schemeName}|status=${delResp.status}|body=${delResp.body}") } } } logger.info("=== SUMMARY ===") logger.info("Total schemes: ${schemes.size()}") logger.info("Kept (used by protected projects): ${keptByUsage}") logger.info("Kept (protected by rules): ${keptByRule}") logger.info("Delete candidates: ${candidates.size()}") candidates.each { c -> logger.info("CANDIDATE|schemeId=${c[0]}|name=${c[1]}|reason=${c[2]}") } logger.info("=== DONE ===")