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

비밀번호

댓글 작성자
 




... 46  [47]  48  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12452정성태12/14/202010439Windows: 183. 설정은 가능하지만 구할 수는 없는 TcpTimedWaitDelay 값
12451정성태12/14/20209609Windows: 182. WMI Namespace를 열거하고, 그 안에 정의된 클래스를 열거하는 방법 [5]
12450정성태12/13/202010337.NET Framework: 983. C# - TIME_WAIT과 ephemeral port 재사용파일 다운로드1
12449정성태12/11/202010711.NET Framework: 982. C# - HttpClient에서의 ephemeral port 재사용 [2]파일 다운로드1
12448정성태12/11/202012348.NET Framework: 981. C# - HttpWebRequest, WebClient와 ephemeral port 재사용파일 다운로드1
12447정성태12/10/202010480.NET Framework: 980. C# - CopyFileEx API 사용 예제 코드파일 다운로드1
12446정성태12/10/202011121.NET Framework: 979. C# - CoCreateInstanceEx 사용 예제 코드파일 다운로드1
12445정성태12/8/20208556오류 유형: 692. C# Marshal.PtrToStructure - The structure must not be a value class.파일 다운로드1
12444정성태12/8/20209356.NET Framework: 978. C# - GUID 타입 전용의 UnmanagedType.LPStruct [1]파일 다운로드1
12443정성태12/8/20209185.NET Framework: 977. C# PInvoke - C++의 매개변수에 대한 마샬링을 tlbexp.exe를 이용해 확인하는 방법
12442정성태12/4/20208092오류 유형: 691. Visual Studio - Build Events에 robocopy를 사용할때 "Invalid Parameter #1" 오류가 발행하는 경우
12441정성태12/4/20207741오류 유형: 690. robocopy - ERROR : No Destination Directory Specified.
12440정성태12/4/20208788오류 유형: 689. SignTool Error: Invalid option: /as
12439정성태12/4/202010033디버깅 기술: 176. windbg - 특정 Win32 API에서 BP가 안 걸리는 경우 (2) [1]
12438정성태12/2/202010003오류 유형: 688. .Visual C++ - Error C2011 'sockaddr': 'struct' type redefinition
12437정성태12/1/20209619VS.NET IDE: 155. pfx의 암호 키 파일을 Visual Studio 없이 등록하는 방법
12436정성태12/1/20209912오류 유형: 687. .NET Core 2.2 빌드 - error MSB4018: The "RazorTagHelper" task failed unexpectedly.
12435정성태12/1/202015384Windows: 181. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (4) - ReuseUnicastPort를 이용한 포트 고갈 문제 해결 [1]파일 다운로드1
12434정성태11/30/202010669Windows: 180. C# - dynamicport 값의 범위를 알아내는 방법
12433정성태11/29/20209702Windows: 179. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (3) - SO_PORT_SCALABILITY파일 다운로드1
12432정성태11/29/202011171Windows: 178. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (2) - SO_REUSEADDR [1]파일 다운로드1
12431정성태11/27/20209101.NET Framework: 976. UnmanagedCallersOnly + C# 9.0 함수 포인터 사용 시 x86 빌드에서 오동작하는 문제파일 다운로드1
12430정성태11/27/20209839오류 유형: 686. Ubuntu - E: The repository 'cdrom://...' does not have a Release file.
12429정성태11/25/20209908디버깅 기술: 175. windbg - 특정 Win32 API에서 BP가 안 걸리는 경우
12428정성태11/25/20208835VS.NET IDE: 154. Visual Studio - .NET Core App 실행 시 dotnet.exe 실행 화면만 나오는 문제
12427정성태11/24/20209982.NET Framework: 975. .NET Core를 직접 호스팅해 (runtimeconfig.json 없이) EXE만 배포해 실행파일 다운로드1
... 46  [47]  48  49  50  51  52  53  54  55  56  57  58  59  60  ...