[CI/CD] Confluence Publisher Plugin Troubleshooting

[CI/CD] Confluence Publisher Plugin Troubleshooting

플러그인 에러로인한 Confluence 포스팅 기능 직접 개발하기

프로젝트에서 CI/CD를 개발하던중 문제가 생겼다. 코드 분석후 보고서 파일을 회사에서 사용하는 컨플루언스에 게시하려고 했는데 자꾸 에러가 나왔다. 문제는 플러그인 자체가 현재 컨플루언스에서 페이지를 생성하지 못했다. 나와 비슷한 문제를 겪은 사람이 이미 3개월 전에 플러그인의 이슈페이지에 해당 문제를 제기했음에도 아직 해결되지 않았다.

[JENKINS-71938] Unable to append content in latest version - Jenkins Jira

문제 범위 파악 - 어디가 문제인가

처음에는 플러그인의 문제일지 Confluence API문제일지 몰랐다. 우선 직접 Confluence API 플러그인이 아니라 직접호출해 글을 게시해 볼 필요가 있었다.

Confluence REST API 문서로 가서 새로운 페이지를 만드는 API를 찾아보았다.
우선 Confluence REST APIv1v2로 나뉘어져 있었는데 어째서인지 v2에는 새로운 페이지를 만드는 Create Content 관련 API가 없었다.

아직 개발을 안한건지, 문서업데이트가 안된건지 몰라도 v1을 볼 수밖에 없었다. 후에 찾아보니 Content가 아니라 Page로 바뀌어서 Create Page가 동일한 역할을 하는 것 같다.

어쨌든 v1을 통해 Postman으로 해당 API를 호출해보았다. 결과는 성공적으로 내 Space에 새로운 글이 생겼다.

문제는 Confluence API가 아닌 Plugin에 있는게 확실해졌다. 따라서 플러그인이 아닌 직접 만든 코드로 보고서를 게시하면 된다.

참고링크 : Confluence API 공식문서


publisher 만들기

우선 팀원과 해당 사항에 대해 대화를 나누고 플러그인 의존성을 제거하고자 직접 파일을 만들어야 할 것 같다는 결론에 도달했다. 전체적인 흐름을 구성해봤다

  • Code Analysis가 끝나면 bug_findercode_prover를 돌렸고 여기서 각각 4개의 html 보고서가 생긴다.

  • 이를 Jenkins의 stash 기능을 통해 Analysis Agent -> Report Agent로 전송한다.

  • Report Agent에서 해당 파일을 unstash하고 해당 에이전트에 위치한 publisher.py 파일에 보고서 파일들을 argument로 넘겨준다.

  • publisher.py는 Confluence API를 직접 호출하고 파일을 게시한다.

큰 흐름은 위와 같다.
세부적인 구현을 위해서는

  1. [Edit pipeline]우선 보고서를 stash하고 unstash하기 위해 파이프라인 스크립트를 수정해야한다.

  2. [Create a Token]이후 Confluence API호출을 위한 토큰을 홈페이지에서 발급받아야 한다. (정확히는 App password를 발급 받아야 한다.)

  3. [Make Module]토큰과 함께 요청을 보내는 코드를 작성한다.

1️⃣Edit Pipeline

ws("${polyspaceWorkspace}"){
  stash includes: "${fileId}-bug_finder\\*.html, ${fileId}-code_prover\\*.html", name: "${polyspaceReports}"
}

분석이 끝나면 ProjectName-Jobname-BuildSeq 방식의 이름을 갖는 고유한 file-id를 폴더명으로 갖는 보고서가 생긴다. (testproejct-pipelinejob-3 이런식) 해당 경로로 가서 Jenkins의 stash기능을 통해 파일을 보관한다.

💡
stash는 파이프라인 간에 데이터를 전달하는 데사용된다. 데이터는 Jenkins 서버에 일시적으로 저장되며, 파이프라인의 다른 단계에서 사용할 수 있다. 모든 작업을 하나의 물리적Agent에서 수행한다면 해당 기능은 필요없겠지만 그렇지 않을 경우 stage가 바뀔때 해당 작업을 수행하는 물리적 머신이 바뀔수도 있기 때문에 파일전송이 필요하다.

statsh는 파일을 이동할 때 사용하는 기능으로 경로를 지정하면 해당 파일을 임시로 Jenkins의 master저장소에 저장한다.

여기서 주의해야할게 stash기능은 빌드결과물과같은 대용량의 파일을 전송하기엔 바람직하지 않은 기능이기 때문에 간단한 파일 전송만 하는게 좋다. 보고서 html파일은 크기가 작기 때문에 stash를 통해 전송해도 무방하다고 판단했다.

실제로 젠킨스 공식문서의 파이프라인 가이드에 가보면 5~100MB의 파일은 다른 방법을 고려하는게 좋다고 한다.
(아래그림 참조 출처: jenkins.io/doc/pipeline/steps/workflow-basi..)

2️⃣Create Token

우선 Confluence에 로그인해 계정 설정에 들어간다.


이후 보안탭에서 API토큰을 생성한다.


토큰을 만들고 나면 secret값이 주어지는데 한번만 표시되므로 잘 보관한다.

3️⃣Make Module

모듈 만드는 일은 내가 다른일을 하는 동안 팀원이 도와주었다. 우선 API를 호출하기 위해선 아래 데이터가 필요하다.

  • confluence domain : https://{조직명}.atlassian.net 의형식인 URL이다.

  • username : 게시할 계정

  • password : 위에서 발급받은 API key의 값

  • space_key : 어느 스페이스에 올릴것인지 알아야 하므로 space key를 입력한다. 이는 confluence에서 계정정보에 가면 나온다. 아래 그림참조

그리고 python 코드를 통해 html파일을 게시한다. 다른 작업을 하는 동안 팀원이 도와줬다. 아래는 코드의 일부분이다. bug_finder폴더의 파일을 읽어들여 API호출시에 실어서 보내준다.

if args.bug_finder_html_path!="":
    bug_finder_html_path = glob.glob(os.path.join(args.bug_finder_html_path, '*.html'))
    for html_file in bug_finder_html_path:
        # HTML 파일에서 내용 읽기
        with open(html_file, 'r', encoding='utf-8') as file:
            print(html_file)
            html_content = file.read()
            html_content = html_content.replace("<!DOCTYPE html>","")
            file_name = os.path.basename(html_file).replace(".html","")

            child_page_data = {
                "type": "page",
                "title": f"bug_finder-{file_name}-{args.job}",
                "ancestors": [{"id": parent_page_id}],
                "space": {"key": space_key},
                "body": {
                    "storage": {
                        "value": html_content,
                        "representation": "storage"
                    }
                }
            }
            response = requests.post(f"{confluence_domain}/wiki/rest/api/content", data=json.dumps(child_page_data),
                                     auth=(username, password), headers={"Content-Type": "application/json"})
            child_page_id = response.json().get('id')

            child_page_link = f"{confluence_domain}/wiki/spaces/{space_key}/pages/{child_page_id}/"
            parent_page_content += f"<p><a href='{child_page_link}'>bug_finder-{file_name}-{args.job}</a></p>"

결과

에러를 뿜뿜 뿜어냈던 report단계가 이제 성공적으로 수행된다. 또한 컨플루언스에도 성공적으로 게시가 된 것을 확인할수 있다.(프로젝트명은 보안상 가렸습니다)

Jenkins의 플러그인은 분명 편하지만 이번에 겪은 것처럼 플러그인이 장애의 원인이 될 수도 있습니다. 플러그인이 잘 작동했다면 분명 편했겠지만 이를 계기로 의존성을 제거해 마음이 편안해졌습니다. 내가 수정할 수 없는 에러를 만났을 때 어떻게 돌아가야 할지 고안하는 과정이 재밌었습니다.


요약

  • 코드 분석 보고서를 confluence에 자동으로 업로드 하기위한 플러그인에서 에러발생

  • 플러그인 대신 직접 모듈 만들어서 API 호출해 글 게시하기

Dependencies 101 : r/ProgrammerHumor