민서네집

Maven Repository Integrity Test (정합성/무결성 테스트) 본문

Python

Maven Repository Integrity Test (정합성/무결성 테스트)

브라이언7 2014. 5. 27. 17:10


maven_repo_check.py


Test Jar(Thread Sleep) Source.zip


Maven Repository 안에 있는 .pom 파일과 .jar 파일은 파일 내용이 정확한지 확인할 수 있는 sha1 파일이 같이 제공된다.


따라서 Python으로 Maven Repository 안에 있는 pom 파일과 jar 파일의 sha1 해쉬값을 구해서 .sha1 파일의 값과 비교해보는 Script를 만들었다.


[사용법]

1. 이 파일을 maven repository directory 에 놓는다. ex) C:\Users\{User Name}\.m2

2. 명령 프롬프트 창에서 python 으로 이 스크립트를 실행시킨다. ex) python maven_repo_check.py

3. Result File: ERROR_LOG_FILE.log


[이 스크립트가 하는 일]

1. 이 스크립트가 실행되는 하위 디렉터리를 모두 체크해서 jar 파일과 pom 파일이 있는지 확인한다.

2. .jar 파일과 .pom 파일이 있는데 동일한 파일명의 .sha1 파일이 없거나, jar 혹은 pom파일의 hash 값이 .sha1 파일의 값과 맞지 않으면 그 디렉터리의 모든 파일을 지운다. => 이 디렉터리를 FAIL 이라고 판단.

3. FAIL 이라고 판단해서 파일을 삭제한 Directory가 파일이나 하위 Directory가 없는 빈 Directory라면 Directory도 삭제한다.

4. FAIL 이라고 판단한 디렉터리의 총 갯수를 화면과 ERROR_LOG_FILE.log 파일에 기록한다.


[주의] 이 스크립트를 실행시키는 디렉터리의 하위 디렉터리를 재귀적으로 모두 검사하기 때문에 반드시 maven repository directory 에 이 스크립트를 두고 실행시킬 것. 그렇지 않다면 jar 파일이 있는 디렉터리의 파일이 모두 삭제되는 불상사가 발생할 수도 있다.


자신의 컴퓨터에 Python이 설치되어 있지 않다면 먼저 Python을 설치해야 하고, python 실행경로를 PATH에 추가해야 한다.



jar 파일의 hash값이 정확히 맞지 않으면 컴파일 시나 실행 시에 에러를 발생시킨다.

특히 실행 시에 에러가 나는 경우 정말 이유를 알 수 없는 에러가 난다.



이 파일을 Maven Repository (ex: C:\Users\{사용자 계정명}\.m2에 넣고, 명령 프롬프트 창에서 

python maven_repo_check.py 라고 실행시킨다.

(물론 자기 컴퓨터에 Python 이 설치되어 있어야 하고, PATH에 Python 이 추가되어 있어야 한다. 이 스크립트는 Python 3.4 버전에서 작성되었고, Python 3.3.3 버전에서도 정상적으로 작동하는 것을 확인했다.)


이 스크립트는 현재 실행시키는 디렉터리의 하위 디렉터리를 재귀적으로 모두 검사하기 때문에 실행시키는 위치가 중요하다.


실행 결과는 같은 디렉터리에 ERROR_LOG_FILE.log 파일이 생긴다.


이 스크립트를 python으로 실행시키면 [Fail] 이라고 출력되는 디렉터리를 자동으로 지운다. maven 을 다시 실행시키면 네트워크에서 다시 다운로드 받아서 아마도 에러가 나지 않을 것이다.


Maven으로 받은 jar 파일의 무결성을 확인하기 위해서 가끔 이 스크립트를 돌려보는 것이 좋을 것 같다.


이 스크립트를 아직 한번도 돌려보지 않다가 돌리는 것이라면 이제까지 별 문제 없었다고 생각했던 자신의 Maven Repository에서 무결성이 어긋난 잘못된 jar 파일이 있었다는 사실에 놀라게 될 것이다.


첨부파일로 추가한 Test Jar(Thread Sleep) Source.zip 파일은 이클립스 등의 IDE에서 사용 중인 경우 jar 파일이 삭제되지 않으므로, 이런 경우 에러 메시지가 제대로 표시되는지 확인하기 위한 용도로 만든 jar 파일과 java 소스를 압축한 것이다.


첨부된 jar 파일을 .sha1 파일에 지정된 jar 파일로 파일명을 바꾸고, java -jar {jar파일명} 으로 실행시키면 단순히 60초 동안 sleep 해서 사용 중인 상태가 된다.

이때 python maven_repo_check.py 를 실행시켜서 무결성이 어긋난 jar 파일이 삭제가 안될때 에러 메시지가 나는지 확인해볼 수 있다.


이 스크립트를 돌릴 때 주의점


이 스크립트는 maven repository 안의 폴더에는 .jar 파일과 .pom 파일에 대해서는 반드시 .sha1 파일이 있다는 가정을 했다. 외부 maven repository 에서 인터넷으로 jar 파일을 다운로드 받는 경우는 그런 것 같은데, 로컬 컴퓨터에서 mvn install 를 하는 경우는 jar파일이 자신의 로컬 maven repository에 설치가 되지만 .sha1 파일은 생기지 않는다. 이 스크립트를 돌리면 이런 경우(자신의 로컬 컴퓨터에서 install한 경우)는 잘못된 jar 파일로 간주하고 jar 파일이 있는 폴더를 지워 버린다.


따라서 자신의 컴퓨터에서 로컬 repository에 install 할 때는 mvn install -DcreateChecksum=true 이렇게 옵션을 줘야지 .sha1 파일이 같이 생긴다.


[참고] http://stackoverflow.com/questions/16958054/how-to-create-md5-checksum-for-tar-files-in-maven



변경사항

[2014-06-02] 

1. aaa.jar.sha1 파일만 있고, aaa.jar 파일이 없는 경우 에러 메시지 없이 도중에 종료되는 버그 수정. => 이런 경우도 Fail Directory로 판단해서 Directory 안의 파일들을 모두 삭제한다.

2. Mac에서 실행시키면 주석문 안의 한글 때문에 실행되지 않으므로 코드 안의 한글 주석을 모두 영어로 변경했음.

3. Mac  OS X 10.9.3 Python 2.7.5 버전에서 정상적으로 작동됨을 확인함.


# maven_repo_check.py
#
# Python Version: Python v3.4.0
# Author: Heeseok Kang
# created date: 2014-05-27
# last updated date: 2014-06-02
#
# usage: 
# 1. Place this file to maven repository directory ex) C:\Users\{User Name}\.m2
# 2. Execute this file with python in Command Line. ex) python maven_repo_check.py
# 3. Result File: ERROR_LOG_FILE.log
# 4. Fail directory is automatically deleted.

# sha1 hash - Calculating Logic
# [origin] http://stackoverflow.com/questions/1869885/calculating-sha1-of-a-file
import hashlib

def hashfile(filepath):
	sha1 = hashlib.sha1()
	f = open(filepath, 'rb')
	try:
		sha1.update(f.read())
	finally:
		f.close()
	return sha1.hexdigest()

import os

def check_sha1(sha1_file_path):
	f = open(sha1_file_path, 'r')
	dir = os.path.dirname(sha1_file_path)

	line = f.readline()
	f.close()
	hash_value_in_file = line[0:40]
	
	# From .sha1 file name, we get the file name of jar or pom file.
	filename = sha1_file_path[0:-5]
	
	if not os.path.isfile(filename):
	  return False
	
	hash_value_by_calc = hashfile(filename)
	if hash_value_in_file == hash_value_by_calc :
		#error_log("[Correct] " + filename)
		return True
	else:
		error_log("[Wrong] " + filename)
		error_log("Correct    Hash: " + hash_value_in_file)
		error_log("Calculated Hash: " + hash_value_by_calc)
		
		return False

# get the full path of current directory.
# http://stackoverflow.com/questions/3430372/how-to-get-full-path-of-current-directory-in-python		
current_path = os.path.dirname(os.path.abspath(__file__))

ERROR_LOG_FILE = current_path + os.path.sep + "ERROR_LOG_FILE.log"
def error_log(msg):
	print(msg)
	f = open(ERROR_LOG_FILE, 'a')
	try:
		f.write(msg+"\n")
	finally:
		f.close()

def isSha1Exist(files, dir):
	for file in files :
		isExist = os.path.isfile(dir + os.path.sep + file + ".sha1")
		if not isExist :
			return False
	return True

# In (dir_path) directory, check whether jar or pom file exists and the sha1 file with same name exists.
# return false if sha1 file doesn't exist or hash value of jar or pom file doesn't match the value in sha1 file.

def check_dir(dir_path):
	jar_files = [name for name in os.listdir(dir_path) if os.path.isfile(dir_path + os.path.sep + name) and name.endswith(".jar")]
	pom_files = [name for name in os.listdir(dir_path) if os.path.isfile(dir_path + os.path.sep + name) and name.endswith(".pom")]
	if not isSha1Exist(jar_files, dir_path) : return False
	if not isSha1Exist(pom_files, dir_path) : return False
	sha1_files = [dir_path + os.path.sep + name for name in os.listdir(dir_path) if os.path.isfile(dir_path + os.path.sep + name) and name.endswith(".sha1")]
	
	isCorrect = True
	for file in sha1_files :
		if not check_sha1(file) :
			isCorrect = False

	return isCorrect

global_fail_dir_count = 0;		
		
import os, glob

# search subdirectory recursively.
# [origin] https://wikidocs.net/39
def search(dirname):
	global global_fail_dir_count
	
	flist = os.listdir(dirname)
	for f in flist:
		next = os.path.join(dirname, f)
		if os.path.isdir(next):
			search(next)
			if not check_dir(next) :
				global_fail_dir_count = global_fail_dir_count + 1;
				wildcard_files = os.path.join(next, "*")
				
				for file in glob.glob(wildcard_files):
					if os.path.isfile(file):
						try:
							os.remove(file)
						except(OSError) as e:
							error_log("[File Delete Error] %s - %s." % (e.filename,e.strerror))
				try:
					os.rmdir(next) # remove if directory is empty or OSError occurs.
					error_log("[Fail Directory Deleted] " + next)
					error_log("----------------------------------------------------------------------------")
				except(OSError) as e:  ## if failed, report it back to the user ##
					error_log("[Directory Delete Error] %s - %s." % (e.filename,e.strerror))
			
import time
def main():
	error_log("");
	error_log("============================================================================")
	error_log("Check Date: " + time.strftime("%Y-%m-%d %H:%M:%S"));
	error_log("----------------------------------------------------------------------------")
	error_log("");
	search(current_path)
	error_log("");
	error_log("----------------------------------------------------------------------------")
	error_log("Total Fail Directory Count is " + str(global_fail_dir_count))
	error_log("============================================================================")
	error_log("");

if __name__ == "__main__":
	main()



[2015-03-27] 추가사항

Python 2.7.8 에서도 실행이 잘 되는데, 이클립스나 STS를 켜 놓고, 이 스크립트를 돌리면, 다음과 같은 에러가 날 때가 있다.


    except(PermissionError) as e:

NameError: global name 'PermissionError' is not defined


검색해 보니, PermissionError 는 Python 2.7 에서는 없고, Python 3.3 에서도 도입된 것이라고 한다.

이런 에러가 나면, 이클립스나 STS를 종료하고, 다시 한번 돌려보세요.

PermissionError 를 OSError 로 스크립트를 수정했다.

스크립트가 비정상 종료되지는 않을 것이고, 에러 메시지가 출력되는 것을 볼 수 있을 것이다.


'Python' 카테고리의 다른 글

Coursera - Python 강의  (0) 2014.09.29
[python] Directory Tree 삭제하기  (0) 2014.05.28
Notepad++에서 파이썬 실행하기  (0) 2014.05.27
python.exe: No module named SimpleHTTPServer  (1) 2014.01.14
Python 배우기  (0) 2013.12.30
Comments