이 문서는 Jira - Scriptrunner로 일정 기간 이상 미접속자 비활성화할 수 있는 스크립트에 대한 가이드를 공유하기 위해 작성되었다.




Jira - Scriptrunner로 일정기간 이상 미접속자 비활성화하기 가이드

Scriptrunner Schedule Job 만들기

ScriptRunner에서는 스케줄에 따라 실행되는 Job을 생성할 수 있다. 스케줄 잡을 생성하기 위해 다음을 수행한다. 


Scriptrunner → Jobs → Create Job 선택한다.



Custom Scheduled Job 선택한다.



아래와 같이 Scheduled Job 화면이 나타나면 제목과 실행시킬 유저 및 Crontab 형식 입력
만약 Crowd 사용하여 사용자를 관리하고 있다면 Using Crowd에 체크 및 24~26 라인 수정한다. 


Interval/Cron expression 기간 입력


Groovy 스크립트 

다음은 Groovy 스크립트의 예시로 적용 환경이나 Jira 버전 혹은 스크립트 러너 버전에 따라 수정이 필요할 수 있다. 


import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.login.LoginManager
import com.atlassian.jira.application.ApplicationRoleAdminService
import com.atlassian.jira.application.ApplicationKeys
import com.atlassian.jira.config.util.JiraHome
import java.io.File

import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import com.onresolve.scriptrunner.parameters.annotation.*
import com.atlassian.jira.bc.user.UserService
import com.atlassian.jira.user.ApplicationUser
import com.onresolve.scriptrunner.runner.customisers.PluginModule
@PluginModule ApplicationRoleAdminService applicationRoleAdminService


@Checkbox(label = "Using Crowd", description = "if you use crowd, check and 24 ~ 26 line write")
Boolean crowdCheck
@ShortTextInput(label = "period", description = "")
String periodString

// 조회 기간 설정(예시: 90일 이상 미접속자)
final int period = periodString?.isInteger() ? periodString as Integer : null

if(period == null){
    return """period is not int. period: ${period}"""
}

// crowd 사용 할 경우 url 및 Application 
final String crowdUrl = "http://10.1.26.30:8095"
final String crowdApplicationName = "interface"
final String crowdApplicationPassword = "password"

String basicAuth = """${crowdApplicationName}:${crowdApplicationPassword}""".getBytes().encodeBase64()

// 로그 파일 설정
String logFileFormat = "user-login-check"
def jiraHome = ComponentAccessor.getComponent(JiraHome)
String jiraHomePath = jiraHome.getHome()
String logDateFileFormat = (new Date().format("yyyy-MM-dd")) as String
String logDate = (new Date().format("yyyy-MM-dd HH:mm:ss")) as String
String os = System.getProperties().get("os.name")
String logPath = ""
if(os.contains("Windows")){
	logPath = """${jiraHomePath}\\log\\${logFileFormat}-${logDateFileFormat}.log"""
} else {
	logPath = """${jiraHomePath}/log/${logFileFormat}-${logDateFileFormat}.log"""
}
File logFile = new File(logPath)
if(!logFile.exists()){
	logFile.createNewFile()
}
def loginManager = ComponentAccessor.getComponent(LoginManager)
def groupManager = ComponentAccessor.getGroupManager()
def userManager = ComponentAccessor.getUserManager()
def unactiveUserList = []
def activeUserList = []
def jiraLicenseGroups = []

// Application License Group 확인
def role = applicationRoleAdminService.getRole(ApplicationKeys.SOFTWARE).get()
role.groups.each{
	jiraLicenseGroups.add(it.getName())
}

userManager.getAllApplicationUsers().each{ ApplicationUser user ->
    if(user.active){
        String userName = user.getUsername()
        String displayName = user.getDisplayName()
        def loginDetails = loginManager.getLoginInfo(userName)
        long lastLoginTime = (loginDetails.getLastLoginTime() ? loginDetails.getLastLoginTime() : 0) as long

        if(lastLoginTime == 0){
            def userMap = ["username": userName, "displayName": displayName, "loginDate": "None"]
            unactiveUserList.add(userMap)
            if(crowdCheck){
                // crowd에서 group 제거
                jiraLicenseGroups.each{ String groupName ->
                    crowdGroupDel(crowdUrl, basicAuth, userMap, groupName)
                }
            } else{
                jiraInactiveuser(userMap, user)
            }
        } else{
            String dateFormat = (new Date(lastLoginTime).format("yyyy-MM-dd HH:mm:ss")) as String
            def userMap = ["username": userName, "displayName": displayName, "loginDate": dateFormat]
            if(new Date(lastLoginTime).compareTo(new Date().plus(-period)) == -1){
                unactiveUserList.add(userMap)
                if(crowdCheck){
                    // crowd에서 group 제거
                    jiraLicenseGroups.each{ String groupName ->
                        crowdGroupDel(crowdUrl, basicAuth, userMap, groupName)
                    }
                } else{
                    jiraInactiveuser(userMap, user)
                }
            } else{
                activeUserList.add(userMap)
            }
        }
    }

}


// Jira에서 해당 유저 비활성화하는 함수
def jiraInactiveuser(Map userMap, ApplicationUser user){
    // 로그 파일 설정
    String logFileFormat = "user-login-check"
    def jiraHome = ComponentAccessor.getComponent(JiraHome)
    String jiraHomePath = jiraHome.getHome()
    String logDateFileFormat = (new Date().format("yyyy-MM-dd")) as String
    String logDate = (new Date().format("yyyy-MM-dd HH:mm:ss")) as String
    String os = System.getProperties().get("os.name")
    String logPath = ""
    if(os.contains("Windows")){
        logPath = """${jiraHomePath}\\log\\${logFileFormat}-${logDateFileFormat}.log"""
    } else {
        logPath = """${jiraHomePath}/log/${logFileFormat}-${logDateFileFormat}.log"""
    }
    File logFile = new File(logPath)
    if(!logFile.exists()){
        logFile.createNewFile()
    }
    def adminUser = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
    def userService = ComponentAccessor.getComponent(UserService)
    def deleteCheck = userService.validateRemoveUserFromApplication(adminUser, user, ApplicationKeys.SOFTWARE)
    if(deleteCheck.isValid()){
         userService.removeUserFromApplication(deleteCheck)
        logFile.append("""${logDate} ${userMap.username}(${userMap.displayName}) Login Date: ${userMap.loginDate} Inactive Success\n""") 
	} else {
        logFile.append("""${logDate} ${userMap.username}(${userMap.displayName}) Login Date: ${userMap.loginDate} Inactive Failed Reason: ${deleteCheck.getErrorCollection().errorMessages.toString()}\n""")
    }
     
}


// Crowd 에서 해당 그룹 제거하는 함수
def crowdGroupDel(String url, String basicAuth, Map userMap, String groupName){
    // 로그 파일 설정
    String logFileFormat = "user-login-check"
    def jiraHome = ComponentAccessor.getComponent(JiraHome)
    String jiraHomePath = jiraHome.getHome()
    String logDateFileFormat = (new Date().format("yyyy-MM-dd")) as String
    String logDate = (new Date().format("yyyy-MM-dd HH:mm:ss")) as String
    String os = System.getProperties().get("os.name")
    String logPath = ""
    if(os.contains("Windows")){
        logPath = """${jiraHomePath}\\log\\${logFileFormat}-${logDateFileFormat}.log"""
    } else {
        logPath = """${jiraHomePath}/log/${logFileFormat}-${logDateFileFormat}.log"""
    }
    File logFile = new File(logPath)
    if(!logFile.exists()){
        logFile.createNewFile()
    }
    def httpBuilder = new HTTPBuilder(url)
    String userName = userMap.username
    httpBuilder.ignoreSSLIssues()
    httpBuilder.request(Method.DELETE){ req ->
        uri.path = "/crowd/rest/usermanagement/1/user/group/direct"
        uri.query = [username: userName, groupname: groupName]
        headers["Content-Type"] = "application/json;charset=utf-8"
        headers["Accept"] = "application/json"
        headers["Authorization"] = "Basic " + basicAuth

        response.success = { resp, json ->
            logFile.append("""${logDate} ${userMap.username}(${userMap.displayName}) Login Date: ${userMap.loginDate} Crowd Delete GroupName: ${groupName} Success\n""")
        }
        response.failure = { resp, json ->
            if(json.reason == "MEMBERSHIP_NOT_FOUND"){
                logFile.append("""${logDate} ${userMap.username}(${userMap.displayName}) Login Date: ${userMap.loginDate} GroupName: ${groupName} already deleted. ${resp.status}\n""")
            } else {
                logFile.append("""${logDate} ${userMap.username}(${userMap.displayName}) Login Date: ${userMap.loginDate} GroupName: ${groupName} Crowd Delete Failed Status: ${resp.status}\n""")
            }
        }
    }
    
}


로그 확인

Scriptrunner → Browse → View server log files 선택한다.



해당 파일 선택 후 보고자 하는 줄 수 입력한다.








  • 레이블 없음