<?php
/**
* JiraAPI
*
* このステータスは例です。JIRAのステータスはカスタマイズ可能なため、各自でご確認下さい。
*
* all status list (id - name)
* 1 : オープン
* 2 : 作業中
* 3 : レビュー中
* 4 : リリース待ち
* 5 : 解決済み
* 6 : 完了
* 7 : Reopen
*
* @see https://developer.atlassian.com/cloud/jira/platform/rest/
*/
class JiraAPI
{
// Class variable
private static $_username = '**** username ****';
private static $_password = '**** password ****';
private static $_domain = '**** JIRA Cloudのドメイン ****';
// @var $_workflowMatrix
// Workflowステータスは更新可能なステータスが限定されているため、
// 目的のWorkflowステータスに遷移できるように導線を構築
//
// 構築には `Get statuses` API を利用し、idと名前を確認し、
// 画面上から遷移経路を確認しマトリクスを作成します。
private static $_workflowMatrix = [
3 => [
1 => [2, 3],
2 => [3],
4 => [7, 2, 3],
7 => [2, 3],
],
4 => [
1 => [2, 3, 4],
2 => [3, 4],
3 => [4],
7 => [2, 3, 4],
],
2 => [
1 => [2],
3 => [7, 2],
4 => [7, 2],
7 => [2],
],
];
private static $_host = 'https://%s.atlassian.net';
private static $_authUrl = '/rest/auth/1/session';
private static $_issueUrl = '/rest/api/2/issue/%s';
private static $_issueGetTransitionsUrl = '/rest/api/2/issue/%s/transitions';
private static $_issueDoTransitionsUrl = '/rest/api/2/issue/%s/transitions';
private static $_statusesUrl = '/rest/api/2/status';
private $_auth;
/**
* __construct
*/
public function __construct()
{
$this->_auth = $this->_auth();
}
/**
* _getUrl
*
* @param string $url
* @param array $args = []
* @return string
*/
private static function _getUrl(string $url, array $args = []): string
{
return sprintf(self::$_host, self::$_domain) . vsprintf($url, $args);
}
/**
* _auth
*
* Cookie-basedの認証形式です。
* OAuth2.0, OAuth1.0a, Basicなどが他にもあります。
*
* @return array
* @throws RuntimeException
* @see https://developer.atlassian.com/cloud/jira/platform/rest/#api-auth-1-session-post
*/
private function _auth(): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-type: application/json',
],
CURLOPT_URL => self::_getUrl(self::$_authUrl),
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'username' => self::$_username,
'password' => self::$_password,
]),
]);
$body = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] !== 200) {
throw new RuntimeException($body, $info['http_code']);
}
return json_decode($body, true);
}
/**
* getIssue
*
* チケットキー情報からチケットID、現在のWorkflowステータスを取得
*
* @param string $key
* @return array
* @throws RuntimeException
* @see https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-get
*/
public function getIssue(string $key): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => [
"Cookie: {$this->_auth['session']['name']}={$this->_auth['session']['value']}",
'Accept: application/json',
],
CURLOPT_URL => self::_getUrl(self::$_issueUrl, [$key]),
]);
$body = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] !== 200) {
throw new RuntimeException($body, $info['http_code']);
}
return json_decode($body, true);
}
/**
* getTransitions
*
* 更新予定のWorkflowステータスのTransitionIDを取得
*
* @param int $issueId
* @return array
* @throws RuntimeException
* @see https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-get
*/
public function getTransitions(int $issueId): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => [
"Cookie: {$this->_auth['session']['name']}={$this->_auth['session']['value']}",
'Accept: application/json',
],
CURLOPT_URL => self::_getUrl(self::$_issueGetTransitionsUrl, [$issueId]),
]);
$body = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] !== 200) {
throw new RuntimeException($body, $info['http_code']);
}
return json_decode($body, true);
}
/**
* updateTransitions
*
* Workflowステータス更新処理
*
* @param int $issueId
* @param int $transitionId
* @throws RuntimeException
* @see https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-transitions-post
*/
public function updateTransitions(int $issueId, int $transitionId)
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => [
"Cookie: {$this->_auth['session']['name']}={$this->_auth['session']['value']}",
'Accept: application/json',
'Content-type: application/json',
],
CURLOPT_URL => self::_getUrl(self::$_issueDoTransitionsUrl, [$issueId]),
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'transition' => [
'id' => $transitionId,
]
]),
]);
$body = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] !== 200 && $info['http_code'] !== 204) {
throw new RuntimeException($body, $info['http_code']);
}
return json_decode($body, true);
}
/**
* getStatuses
*
* Workflowステータス一覧を取得
* 実運用上、本関数は呼ばれません。
*
* @return array
* @throws RuntimeException
* @see https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-status-get
*/
public function getStatuses(): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => [
"Cookie: {$this->_auth['session']['name']}={$this->_auth['session']['value']}",
'Accept: application/json',
],
CURLOPT_URL => self::_getUrl(self::$_statusesUrl),
]);
$body = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] !== 200) {
throw new RuntimeException($body, $info['http_code']);
}
return json_decode($body, true);
}
/**
* autoTransitions
*
* @param int $issueId
* @param int $fromStatus
* @param int $toStatus
* @return array
* @throws RuntimeException
*/
public function autoTransitions(int $issueId, int $fromStatus, int $toStatus): array
{
$ret = [];
if (isset(self::$_workflowMatrix[$toStatus]) &&
isset(self::$_workflowMatrix[$toStatus][$fromStatus])) {
foreach (self::$_workflowMatrix[$toStatus][$fromStatus] as $status) {
$transitions = $this->getTransitions($issueId);
if (0 < count($transitions)) {
foreach ($transitions['transitions'] as $transition) {
if (!empty($transition['to']['id']) && $transition['to']['id'] == $status) {
$this->updateTransitions($issueId, $transition['id']);
break;
}
}
}
}
}
return $ret;
}
}
$secret = '**** Webhookに登録したSecret情報 ****';
$payloadPrefix = 'payload=';
$actionTypeLabeled = 'labeled';
// @var $relationGitHubJira
// GitHub の Label に応じた Jira のWorkflowステータスのステータスIDです
$relationGitHubJira = [
'レビュー依頼' => 3,
'確認済み' => 4,
'差し戻し' => 2,
];
$header = getallheaders();
$postJson = file_get_contents('php://input');
$hmac = hash_hmac('sha1', $postJson, $secret);
$payloadLen = strlen($payloadPrefix);
if (isset($header['X-Hub-Signature']) && $header['X-Hub-Signature'] === "sha1={$hmac}") {
if (substr($postJson, 0, $payloadLen) === $payloadPrefix) {
$postArray = json_decode(urldecode(substr($postJson, $payloadLen)), true);
} else {
$postArray = json_decode($postJson, true);
}
try {
switch ($postArray['action']) {
case $actionTypeLabeled:
if (isset($relationGitHubJira[$postArray['label']['name']])) {
//smart commit の仕様を踏襲し、ブランチ名の先頭にチケットキー情報を設定しています
preg_match('/Axxx-[d]+/u', $postArray['pull_request']['head']['ref'], $matches);
if (1 === count($matches)) {
$jira = new JiraAPI();
$issue = $jira->getIssue($matches[0]);
$jira->autoTransitions($issue['id'], $issue['fields']['status']['id'], $relationGitHubJira[$postArray['label']['name']]);
}
}
break;
}
} catch (RuntimeException $re) {
} catch (Exception $e) {
}
}