Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

C# - JIRA에 등록된 Project의 Version 항목 추가하는 방법

사내에서 이슈 관리 시스템인 JIRA를 사용하면서 불편한 점이 하나 나왔는데요. 이슈 생성할 때, Version 항목이 미리 등록되어 있지 않으면 그걸 또 등록하러 가야 한다는 것입니다. 이런 불편함을, 제품의 빌드 스크립트에서 버전을 JIRA에 등록하는 방법으로 해결할 수 있습니다. 그리고 이를 위해 JIRA REST API를 사용하게 된 것입니다.

C# - JIRA REST API 사용 정리
; https://www.sysnet.pe.kr/2/0/11566

자, 그럼 시작해 볼까요? ^^

우선, 해당 프로젝트에 등록된 모든 버전을 가져오는 version API를,

/rest/api/2/project/{projectIdOrKey}/versions
; https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/#d2e231

다음과 같이 작성할 수 있습니다.

public async Task<string> GetVersions(string projectKey)
{
    string url = _baseUrl + $"project/{projectKey}/versions";
    HttpResponseMessage hrm = await _httpClient.GetAsync(url);

    string text = await hrm.Content.ReadAsStringAsync();
    return text;
}

출력 결과로부터 json2csharp을 통해 다음과 같은 Entity 클래스를 구하고,

public class Version /* RootObject */
{
    public string self { get; set; }
    public string id { get; set; }
    public string name { get; set; }
    public bool archived { get; set; }
    public bool released { get; set; }
    public string releaseDate { get; set; }
    public string userReleaseDate { get; set; }
    public int projectId { get; set; }
    public bool? overdue { get; set; }
    public string description { get; set; }
    public string startDate { get; set; }
    public string userStartDate { get; set; }
}

API를 다시 다음과 같은 코드로 바꿀 수 있습니다.

public async Task<Version[]> GetVersions(string projectKey)
{
    string url = _baseUrl + $"project/{projectKey}/versions";
    HttpResponseMessage hrm = await _httpClient.GetAsync(url);

    string text = await hrm.Content.ReadAsStringAsync();
    Version [] versionList = Newtonsoft.Json.JsonConvert.DeserializeObject<Version[]>(text);

    return versionList;
}

이를 기반으로, "버전 이름"에 해당하는 항목이 등록되어 있는지 알 수 있는 API를 제공할 수 있습니다.

public async Task<Version> GetVersionByName(string projectKey, string versionName)
{
    Version [] versions = await GetVersions(projectKey);
    return versions.FirstOrDefault((e) => e.name == versionName);
}

만약 없다면 새롭게 등록해야 하는데요, 이를 위한 REST API는 다음과 같습니다.

/rest/api/2/version
; https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/#d2e3548

이때 POST로 Json 데이터를 보내야 하는데 다음과 같은 형식입니다.

{
    "description": "An excellent version",
    "name": "New Version 1",
    "archived": false,
    "released": true,
    "releaseDate": "2010-07-06",
    "userReleaseDate": "6/Jul/2010",
    "project": "PXA",
    "projectId": 10000
}

POST 데이터의 예제가 저렇게 나오긴 하지만 약간의 규칙이 있습니다.

  • releaseDate와 userReleaseDate는 동시에 보낼 수 없음. (즉, releaseDate만 보내면 됨)
  • projectId가 있으면 project 항목은 안 보내도 됨
  • 만약 startDate 필드가 있다면 반드시 releaseDate 날짜 보다 이전이어야 함.

여기서 애매한 것이, "projectId" 필드입니다. 보통 "project"는 JIRA 웹 사이트에서 이슈 번호를 통해 쉽게 인지를 하고 있지만 projectId의 경우에는 모르는 경우가 많습니다. 따라서 projectKey로 Id를 구해주는 API를 하나 만들어 두는 것이 좋습니다.

/rest/api/2/project/{projectIdOrKey}
; https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/#d2e208

public async Task<Project> GetProjectByKey(string projectKey)
{
    string url = _baseUrl + $"project/{projectKey}";
    HttpResponseMessage hrm = await _httpClient.GetAsync(url);

    string text = await hrm.Content.ReadAsStringAsync();
    Project project = Newtonsoft.Json.JsonConvert.DeserializeObject<Project>(text);

    return project;
}

이쯤에서 우리가 구현하고 싶은 코드를 정리해 보면 다음과 같습니다.

{
    string newVersionName = "myapp-1.0.1";
    string projectKey = "myProject";

    Project project = await jira.GetProjectByKey(projectKey);
    if (project == null)
    {
        Console.WriteLine("NO Project: " + projectKey);
        return;
    }

    JiraEntity.Version result = await jira.GetVersionByName(projectKey, newVersionName);
    if (result == null)
    {
        // 기존에 등록된 버전이 없다면 신규 등록
        JiraEntity.Version version = new JiraEntity.Version();
        version.name = newVersionName;
        version.projectId = Int32.Parse(project.id);
        version.description = versionDesc;
        version.startDate = DateTime.Now.ToString("yyyy-MM-dd");
        SetReleaseDate(version, releaseDate);

        result = await jira.CreateVersion(version);
        if (result != null)
        {
            return;
        }
    }
    else
    {
        // 기존에 등록된 버전이 있다면 업데이트
        result.description = versionDesc;
        SetReleaseDate(result, releaseDate);

        result = await jira.UpdateVersion(result);
        if (result != null)
        {
            return;
        }
    }
}

static void SetReleaseDate(JiraEntity.Version version, string releaseDate)
{
    if (releaseDate == null)
    {
        return;
    }

    version.releaseDate = releaseDate;
    version.released = true;
}

이를 위해 새로운 버전을 등록하는 CreateVersion 메서드는 이렇게 만들어 주고,

public async Task<JiraEntity.Version> CreateVersion(JiraEntity.Version version)
{
    string url = _baseUrl + "version";

    string postData = Newtonsoft.Json.JsonConvert.SerializeObject(version);

    StringContent content = new StringContent(postData, Encoding.UTF8, "application/json");
    HttpResponseMessage hrm = await _httpClient.PostAsync(url, content);

    string text = await hrm.Content.ReadAsStringAsync();
    JiraEntity.Version registeredVersion = Newtonsoft.Json.JsonConvert.DeserializeObject<JiraEntity.Version>(text);

    return registeredVersion;
}

기존 버전 정보를 업데이트하는 UpdateVersion 메서드는 다음의 REST API를,

/rest/api/2/version/{id}
; https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/#d2e3578

PUT으로 호출하면 되므로 이렇게 만들어 줍니다.

public async Task<JiraEntity.Version> UpdateVersion(JiraEntity.Version version)
{
    string url = _baseUrl + "version/" + version.id;
    MakeVersionPutValid(version);

    string postData = Newtonsoft.Json.JsonConvert.SerializeObject(version);

    StringContent content = new StringContent(postData, Encoding.UTF8, "application/json");
    HttpResponseMessage hrm = await _httpClient.PutAsync(url, content);

    string text = await hrm.Content.ReadAsStringAsync();
    JiraEntity.Version registeredVersion = Newtonsoft.Json.JsonConvert.DeserializeObject<JiraEntity.Version>(text);

    return registeredVersion;
}

뭐... 이 정도까지 했으니, 이제 JIRA 정도는 자유자재로 다루실 수 있겠죠?!!! ^^

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




참고로, REST API에 Post 호출을 했는데 다음과 같은 "HTTP Status 415 - Unsupported Media Type" 오류가 반환된다면?

<html><head><title>Apache Tomcat/7.0.29 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 415 - Unsupported Media Type</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>Unsupported Media Type</u></p><p><b>description</b> <u>The server refused this request because the request entity is in a format not supported by the requested resource for the requested method (Unsupported Media Type).</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.29</h3></body></html>

Post 요청 헤더에 "application/json"을 설정하지 않아서 그런 것입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 7/2/2018]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13402정성태8/20/20233326닷넷: 2137. ILSpy의 nuget 라이브러리 버전 - ICSharpCode.Decompiler
13401정성태8/19/20233562닷넷: 2136. .NET 5+ 환경에서 P/Invoke의 성능을 높이기 위한 SuppressGCTransition 특성 [1]
13400정성태8/10/20233427오류 유형: 874. 파이썬 - pymssql을 윈도우 환경에서 설치 불가
13399정성태8/9/20233443닷넷: 2135. C# - 지역 변수로 이해하는 메서드 매개변수의 값/참조 전달
13398정성태8/3/20234211스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
13397정성태7/23/20233724닷넷: 2134. C# - 문자열 연결 시 string.Create를 이용한 GC 할당 최소화
13396정성태7/22/20233425스크립트: 54. 파이썬 pystack 소개 - 메모리 덤프로부터 콜 스택 열거
13395정성태7/20/20233345개발 환경 구성: 685. 로컬에서 개발 중인 ASP.NET Core/5+ 웹 사이트에 대해 localhost 이외의 호스트 이름으로 접근하는 방법
13394정성태7/16/20233317오류 유형: 873. Oracle.ManagedDataAccess.Client - 쿼리 수행 시 System.InvalidOperationException
13393정성태7/16/20233482닷넷: 2133. C# - Oracle 데이터베이스의 Sleep 쿼리 실행하는 방법
13392정성태7/16/20233353오류 유형: 872. Oracle - ORA-01031: insufficient privileges
13391정성태7/14/20233425닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요?
13390정성태7/12/20233378스크립트: 53. 파이썬 - localhost 호출 시의 hang 현상
13389정성태7/5/20233424개발 환경 구성: 684. IIS Express로 호스팅하는 웹을 WSL 환경에서 접근하는 방법
13388정성태7/3/20233562오류 유형: 871. 윈도우 탐색기에서 열리지 않는 zip 파일 - The Compressed (zipped) Folder '[...].zip' is invalid. [1]파일 다운로드1
13387정성태6/28/20233581오류 유형: 870. _mysql - Commands out of sync; you can't run this command now
13386정성태6/27/20233661Linux: 61. docker - 원격 제어를 위한 TCP 바인딩 추가
13385정성태6/27/20233877Linux: 60. Linux - 외부에서의 접속을 허용하기 위한 TCP 포트 여는 방법
13384정성태6/26/20233621.NET Framework: 2131. C# - Source Generator로 해결하는 enum 박싱 문제파일 다운로드1
13383정성태6/26/20233373개발 환경 구성: 683. GPU 런타임을 사용하는 Colab 노트북 설정
13382정성태6/25/20233412.NET Framework: 2130. C# - Win32 API를 이용한 윈도우 계정 정보 (예: 마지막 로그온 시간)파일 다운로드1
13381정성태6/25/20233798오류 유형: 869. Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
13380정성태6/24/20233240스크립트: 52. 파이썬 3.x에서의 동적 함수 추가
13379정성태6/23/20233250스크립트: 51. 파이썬 2.x에서의 동적 함수 추가
13378정성태6/22/20233154오류 유형: 868. docker - build 시 "CANCELED ..." 뜨는 문제
13377정성태6/22/20236982오류 유형: 867. 파이썬 mysqlclient 2.2.x 설치 시 "Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually" 오류
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...