성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - JIRA REST API 사용 정리 (1) Basic 인증</h1> <p> JIRA REST API를 사용하는 방법을 간단하게 정리해 봅니다. ^^ 문서는 이미 다음과 같이 잘 공개되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Jira REST API examples ; <a target='tab' href='https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/'>https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/</a> </pre> <br /> 사용할 수 있는 API 종류는 다음의 경로에서 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Index of ./software/jira/docs/api/REST ; <a target='tab' href='https://docs.atlassian.com/software/jira/docs/api/REST/'>https://docs.atlassian.com/software/jira/docs/api/REST/</a> </pre> <br /> 가령, 여러분들의 회사에 설치된 JIRA 시스템의 버전이 6.1.4라면 다음과 같이 지원 API 문서를 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > JIRA 6.1.4 REST API documentation ; <a target='tab' href='https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/'>https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/</a> </pre> <br /> 이 글에서는 (제 환경이 ^^; 6.1.4 버전이므로) 위의 문서를 대상으로 진행합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 쿼리를 날려 볼까요? ^^ 예를 들어 다음과 같은 조건에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 계정: testuser 암호: pass@word JIRA 서버: jira.test.com </pre> <br /> JIRA에 자신에게 할당된 이슈를 확인하는 요청을 (Windows 10에서도 제공하는) curl을 이용해 다음과 같이 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [특정 사용자에게 할당된 Issue를 검색하는 쿼리: <a target='tab' href='https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/#d2e3178'>search</a>] curl -u [JIRA계정]:[암호] -X GET -H "Content-Type: application/json" http://[JIRA 서버]/rest/api/latest/search?jql=assignee=[사용자] </pre> <br /> 여기에 -v 옵션을 더하면 요청 및 응답 헤더를 함께 출력으로 보여주기 때문에 C# 코드로 어떻게 인증해야 하는지를 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > curl <span style='color: blue; font-weight: bold'>-v</span> -u <span style='color: blue; font-weight: bold'>testuser:pass@word</span> -X GET -H "Content-Type: application/json" http://jira.test.com/rest/api/latest/search?jql=<span style='color: blue; font-weight: bold'>assignee=testuser</span> </pre> <br /> 위와 같이 실행하면 요청 및 응답 헤더가 다음과 같은 식으로 출력됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\>curl <span style='color: blue; font-weight: bold'>-v</span> -u <span style='color: blue; font-weight: bold'>testuser:pass@word</span> -X GET -H "Content-Type: application/json" http://jira.test.com/rest/api/latest/search?jql=<span style='color: blue; font-weight: bold'>assignee=testuser</span> Note: Unnecessary use of -X or --request, GET is already inferred. * Trying 192.168.100.50... * TCP_NODELAY set * Connected to jira.test.com (192.168.100.50) port 80 (#0) * Server auth using Basic with user 'testuser' > GET /rest/api/latest/search?jql=assignee=testuser HTTP/1.1 > Host: jira.test.com > <span style='color: blue; font-weight: bold'>Authorization: Basic dGVzdHVzZXI6cGFzc0B3b3Jk</span> > User-Agent: curl/7.55.1 > Accept: */* > Content-Type: application/json > < HTTP/1.1 200 OK < Date: Thu, 28 Jun 2018 00:27:30 GMT < Server: Apache/2.2.17 (Unix) mod_jk/1.2.31 < X-AREQUESTID: 567x639827x1 < <span style='color: blue; font-weight: bold'>Set-Cookie: JSESSIONID=FA156...[생략]...FD0FA; Path=/; HttpOnly</span> < X-Seraph-LoginReason: OK < <span style='color: blue; font-weight: bold'>Set-Cookie: atlassian.xsrf.token=A7M3-BI7E-1YXF-MHP5|3c5ba...[생략]...07747|lin; Path=/</span> < X-ASESSIONID: 9pof4z < X-AUSERNAME: testuser < Cache-Control: no-cache, no-store, no-transform < Transfer-Encoding: chunked < Content-Type: application/json;charset=UTF-8 < ...[내용 생략]... </pre> <br /> 아하... Basic 인증 방식을 사용하고 있고, 응답으로 JSESSIONID, atlassian.xsrf.token을 Cookie로 내려주고 있습니다. 자, 그럼 2가지 방식으로 JIRA REST API를 호출할 수 있습니다.<br /> <br /> <ol> <li>인증을 위한 REST API를 호출 후, 이후의 요청은 JSESSIONID, atlassian.xsrf.token을 전달</li> <li>모든 인증마다 BASIC 인증 헤더를 전달</li> </ol> <br /> 이 글에서는 1번 방식을 사용할 텐데요, 그런데 딱히 REST API에 대한 로그인 전용 쿼리가 없으므로 이를 대신할 적당한 API 후보를 찾아야 합니다. 문서를 보니, /rest/api/2/myself 정도가 적당한 것 같습니다. 따라서, 우리 나름대로 Login API를 다음과 같이 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { class Jira { const string QUERY_URL_FORMAT = "http://{0}/rest/api/latest/{1}"; string _baseUrl; CookieContainer _cookies; HttpClient _httpClient; public async Task<bool> Login(string jiraServer, string userId, string password) { string url = string.Format(QUERY_URL_FORMAT, jiraServer, "myself"); string authHeader = CreateBasicAuth(userId, password); HttpClientHandler handler = new HttpClientHandler(); <span style='color: blue; font-weight: bold'>_cookies = new CookieContainer(); handler.CookieContainer = _cookies;</span> HttpClient hc = new HttpClient(handler); hc.DefaultRequestHeaders.Add("Authorization", authHeader); HttpResponseMessage hrm = await hc.GetAsync(url); if (<span style='color: blue; font-weight: bold'>hrm.StatusCode == System.Net.HttpStatusCode.Unauthorized</span>) { return false; } _baseUrl = string.Format(QUERY_URL_FORMAT, jiraServer, ""); _httpClient = hc; return true; } private string CreateBasicAuth(string userId, string password) { string text = userId + ":" + password; byte[] buf = Encoding.UTF8.GetBytes(text); return "Basic " + Convert.ToBase64String(buf); } } } </pre> <br /> 사용은 이렇게 해주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static async Task Main(string[] args) { (string id, string password) = ("testuser", "pass@word"); string jiraServer = "jira.test.com"; <span style='color: blue; font-weight: bold'>Jira jira = new Jira(); if (await jira.Login(jiraServer, id, password) == false)</span> { Console.WriteLine("Auth failed: " + id); return; } Console.WriteLine("Connected"); } </pre> <br /> 자, 그럼 이제 개별 REST API를 C#으로 래핑하는 작업을 하나씩 해주시면 됩니다. 가령, 해당 사용자에게 할당된 모든 이슈를 가져오고 싶다면 <a target='tab' href='https://docs.atlassian.com/software/jira/docs/api/REST/6.1.4/'>6.1.4 버전의 API 문서</a>에 따라, /rest/api/2/search API를 다음과 같이 추가할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... namespace ConsoleApp1 { class Jira { // ...[생략]... <span style='color: blue; font-weight: bold'>public async Task<string> GetIssuesByAssignee(string projectKey, string assignee)</span> { string url = _baseUrl + "search?jql=assignee=" + assignee + " and project=" + projectKey; HttpResponseMessage hrm = await _httpClient.GetAsync(url); string result = await hrm.Content.ReadAsStringAsync(); return result; } } } </pre> <br /> 사용법은 다음과 같이 해주면 되겠고.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static async Task Main(string[] args) { (string id, string password) = ("testuser", "pass@word"); string jiraServer = "jira.test.com"; Jira jira = new Jira(); if (await jira.Login(jiraServer, id, password) == false) { Console.WriteLine("Auth failed: " + id); return; } <span style='color: blue; font-weight: bold'>string result = await jira.GetIssuesByAssignee("myProject", id);</span> Console.WriteLine(result); } </pre> <br /> 이후 원하는 만큼 API 호출을 추가하면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> API 호출에서 한 가지 아쉬운 점이 있다면 문자열 반환입니다. 이 부분을 좀 더 멋있게 역직렬화하면 좋을 듯한데요. GetIssuesByAssignee 메서드의 결과물을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > {"expand":"schema,names","startAt":0,"maxResults":50,"total":358,"issues":[{"expand":"editmeta,renderedFields,transitions,changelog,operations",...[생략]...,"versions":[],"environment":null,"timeestimate":null,"customfield_10300":null,"aggregateprogress":{"progress":0,"total":0},"lastViewed":null,"timeoriginalestimate":null,"aggregatetimespent":null}}]} </pre> <br /> 너무 복잡하므로 이것을 그대로 json 확장자의 파일로 저장해 Visual Studio에서 열고 마우스 우클릭으로 "Format Document" 메뉴를 실행하면 다음과 같이 깔끔하게 포맷팅이 됩니다.<br /> <br /> <img alt='jira_rest_api_1.png' src='/SysWebRes/bbs/jira_rest_api_1.png' /><br /> <br /> 이것을 보고 C# POCO 타입들을 만들어 나갈 수 있습니다. 하지만... 언제 저걸 다 작성하겠습니까? 그냥 다음의 사이트를 방문해서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > json2csharp ; <a target='tab' href='http://json2csharp.com/'>http://json2csharp.com/</a> </pre> <br /> 위의 json 텍스트를 붙여 넣고 "Generate" 버튼을 누르면 C# 타입들이 자동으로 생성됩니다. ^^ 이것을 프로젝트에 추가하고, 단지 "RootObject" 타입의 이름만 "SearchResult"로 바꾸겠습니다.<br /> <br /> 자... 그럼 이제 Newtonsoft.Json을 이용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Install-Package Newtonsoft.Json -Version 11.0.2 </pre> <br /> 다음과 같이 역직렬화한 클래스를 반환할 수 있게 되었고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public async <span style='color: blue; font-weight: bold'>Task<SearchResult></span> GetIssuesByAssignee(string projectKey, string assignee) { string url = _baseUrl + "search?jql=assignee=" + assignee + " and project=" + projectKey; HttpResponseMessage hrm = await _httpClient.GetAsync(url); string text = await hrm.Content.ReadAsStringAsync(); <span style='color: blue; font-weight: bold'>SearchResult result = Newtonsoft.Json.JsonConvert.DeserializeObject<SearchResult>(text);</span> return result; } </pre> <br /> 형식 안정성을 기반으로 한 인텔리센스의 도움으로 좀 더 편리하게 코딩을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>SearchResult result</span> = await jira.GetIssuesByAssignee(projectKey, assignee); foreach (var issue in <span style='color: blue; font-weight: bold'>result.issues</span>) { Console.WriteLine(<span style='color: blue; font-weight: bold'>issue.key</span>); } </pre> <br /> 이 정도면... 대충 설명이 끝난 것 같군요. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1279&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 물론 소스 코드가 공개되어 있는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > jonas0007/Jira.SDK ; <a target='tab' href='https://github.com/jonas0007/Jira.SDK'>https://github.com/jonas0007/Jira.SDK</a> </pre> <br /> 라이브러리를 NuGet으로부터 다운로드해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Jira.SDK ; <a target='tab' href='https://www.nuget.org/packages/Jira.SDK/'>https://www.nuget.org/packages/Jira.SDK/</a> Install-Package Jira.SDK -Version 1.2.25 </pre> <br /> 다음과 같이 편안하세 사용하셔도 좋습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://github.com/jonas0007/Jira.SDK'>https://github.com/jonas0007/Jira.SDK</a> Jira jira = new Jira(); //Connect to Jira with username and password. Please be aware that the information returned by the Jira REST API depends on the access rigths of the user. jira.Connect("{{JIRA URL}}", "{{USERNAME}}", "{{PASSWORD}}"); //You can also connect to Jira anonymously. Please make sure that the information you want to request with the SDK is accessible by unauthenticated users. jira.Connect("{{JIRA URL}}"); //Gets all of the projects configured in your jira instance List<Project> projects = jira.GetProjects(); //Gets a specific project by name Project project = jira.GetProject("{{projectname}}"); //Gets all of users favourite filters List<IssueFilter> filters = jira.GetFilters(); //Gets a specific filter by name IssueFilter filter = jira.GetFilter("{{filtername}}"); //Get a list of agile boards configured in your jira instance List<AgileBoard> agilaboards = jira.GetAgileBoards(); //Get a specific issue with key Issue issue = jira.GetIssue("{{issuekey}}"); //Add a new issue to a project Project project = jira.GetProject("{{projectname}}"); Issue newIssue = project.AddIssue(new IssueFields() { Summary = "Summary of the new issue", IssueType = new IssueType(0, "Type"), CustomFields = new Dictionary<string, CustomField>() { { "customfield_11000", new CustomField(11000, "Value") } } }); ); </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1771
(왼쪽의 숫자를 입력해야 합니다.)