【Python】Backlog API を叩いて課題一覧を Excel に出力してみる【openpyxl】

こんにちは。
とある事情で月次で発生した障害情報を Excel に纏めて提出する必要が出てきたので、スクリプトを組んでみようと思います。
発生した障害は Backlog の Webhook 機能を使って課題として登録されているので、Backlog API を叩いて月の障害課題を収集します。
Python には openpyxl という Excel 操作用のライブラリが用意されているようなので、甘んじて使ってみます。
※ Python よく分からない民なので、割とむちゃくちゃしてるかもです。

ディレクトリ構成

テンプレートとなる Excel ファイルを templates ディレクトリ配下に配置しておき、API を叩いて取得した課題一覧をマージして output に出力する構成にしてみます。

.
├── main.py
├── output
└── templates
    └── template.xlsx

スクリプト

念のため API Key、スペース ID、プロジェクト ID 等の秘匿情報周りは環境変数から読み込めるようにしておきます。
また、課題の検索期間、検索キーワードはコマンドライン引数から指定できるように調整します。

#!/usr/bin/python3
# coding: utf-8

import json
import os
import urllib.request
import urllib.parse
import datetime
import sys
import openpyxl
import argparse

parser = argparse.ArgumentParser(
    usage='Specify the collection period in -p and the search word in -w as arguments',
    description='Output a list of issues to Excel by hitting the BackLog API.'
    )
parser.add_argument('-p', '--period', required=True, help='Specify the time period for collecting issues (1: this month to today or 2: last month)')
parser.add_argument('-w', '--word', required=True, help='Specify search words')
args = parser.parse_args()

if __name__ == '__main__':
    if args.period == "1":
        CREATED_SINCE = datetime.datetime.now().strftime('%Y' + "-" + '%m') + '-01'
        CREATED_UNTIL = datetime.datetime.now().strftime('%Y' + "-" + '%m' + "-" + '%d')
    elif args.period == "2":
        CREATED_SINCE = (datetime.datetime.today() - datetime.timedelta(datetime.datetime.today().day)).strftime('%Y-%m') + '-01'
        CREATED_UNTIL = (datetime.datetime.today() - datetime.timedelta(datetime.datetime.today().day)).strftime('%Y-%m-%d')
    else:
        print('Invalid argument.')
        sys.exit(1)

BACKLOG_API_KEY = os.environ['BACKLOG_API_KEY']
SPACE_ID = os.environ['SPACE_ID']
PROJECT_ID = os.environ['PROJECT_ID']
KEYWORD = urllib.parse.quote(args.word)
#CREATED_SINCE = '2022-xx-01'
#CREATED_UNTIL = '2022-xx-31'

URL = f'https://{SPACE_ID}.backlog.jp/api/v2/issues?apiKey={BACKLOG_API_KEY}&projectId[]={PROJECT_ID}&createdSince={CREATED_SINCE}&createdUntil={CREATED_UNTIL}&count=100&keyword={KEYWORD}'

def utc_to_jst(timestamp_utc):
    """
    @string
    UTC → JST に変換する
    """
    datetime_utc = datetime.datetime.strptime(timestamp_utc, "%Y-%m-%dT%H:%M:%SZ")
    datetime_jst = datetime_utc + datetime.timedelta(hours=9)
    timestamp_jst = datetime.datetime.strftime(datetime_jst, "%Y-%m-%d %H:%M:%S")
    return timestamp_jst
    
def get_alarm_list(url):
    """
    @string
    BackLog の課題を取得しパースしてから配列に突っ込む
    """
    request = urllib.request.Request(url)
    with urllib.request.urlopen(request) as response:
        data = json.loads(response.read().decode("utf-8"))

        lists = []
        for i in range(len(data)):
            lists.append(utc_to_jst(data[i]['created']) + "," + data[i]['summary'])
    return lists

"""
# NOTE: BackLog API 叩く
"""
res = get_alarm_list(URL)

"""
# NOTE: テンプレート( Excel )読み込み
"""
wb = openpyxl.load_workbook("templates/template.xlsx")
cover_ws = wb.worksheets[0]
list_ws = wb.worksheets[1]

"""
# NOTE: 表紙に資料作成日を追記
"""
cover_ws.cell(column=10, row=13, value=datetime.datetime.now().strftime('%Y' + "-" + '%m' + "-" + '%d'))

"""
# NOTE: 全課題を Excel シートに突っ込む
"""
for i in range(len(res)):
    list_ws.cell(column=3, row=(i + 3), value=res[i].split(',')[0])
    list_ws.cell(column=4, row=(i + 3), value=res[i].split(',')[1])
    print(res[i])

"""
# NOTE: Excel 保存
"""
wb.save("output/MonthlyAlarm_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".xlsx")

openpyxl 以外はビルトインのモジュールを使ってみました。
※ いつも通りエラーハンドリングは 無視 省力です。自分用の備忘録ですしおすし。

【PHP】Cookie を保存/送信して認証が必要なページをスクレイピングしてみる【curl】

こんにちは。
先日、CakePHP で作られた外部システムからデータを引っ張ってくる必要がありました。
ただ、データ取得用の API 等が用意されているわけでもなかったため、curl で叩いてデータを引っ張ってくることにします。

最初のログインページの認証後に Cookie を維持する必要があったので、その処理を PHP で実装してみます。

コード

PHPには cURL 関数が用意されているのでありがたく使わせて頂きます。

<?php

class PHPCurl {
    
    private $getCookieUrl;
    private $targetUrl;
    private $username;
    private $password;
    private $saveCookieFile;

    /**
     * コンストラクタ
     * 
     * @param string $getCookieUrl Cookie取得用のURL
     * @param string $targetUrl    CurlのターゲットURL
     * @param string $username     ログイン情報(ユーザ名)
     * @param string $password     ログイン情報(パスワード)
     */
    public function __construct($getCookieUrl, $targetUrl, $username, $password){
        $this->getCookieUrl = $getCookieUrl;
        $this->targetUrl = $targetUrl;
        $this->username = $username;
        $this->password = $password;
        $this->saveCookieFile = stream_get_meta_data($fp = tmpfile());
    }

    /**
     * Cookie取得用のメソッド
     */
    public function getCookie(){
        $data = array(
            'data[User][username]' => $this->username,
            'data[User][password]' => $this->password,
        );

        $curl = curl_init($this->getCookieUrl);
        curl_setopt($curl, CURLOPT_POST, TRUE);
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));

        curl_setopt($curl, CURLOPT_COOKIEJAR, $this->saveCookieFile['uri']);
        curl_setopt($curl, CURLOPT_COOKIEFILE, $this->saveCookieFile['uri']);
        curl_exec($curl);
        curl_close($curl);
    }

    /**
     * コンテンツ取得用のメソッド
     * 
     * @return string HTMLコンテンツ
     */
    public function getContent(){
        $curl = curl_init($this->targetUrl);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_COOKIEFILE, $this->saveCookieFile['uri']);
        $result = curl_exec($curl);
        curl_close($curl);

        return $result;
    }
}

$curlInfo = new PHPCurl('https://xxxxxx.xxx/login', 'https://xxxxxx.xxx/target', 'finger', 'ease');

$curlInfo->getCookie();
$content = $curlInfo->getContent();

echo $content;

※ POST の bodyデータ は適宜置き換えてくださいね。
あとは必要なデータをパースするだけです。

おまけ

curl だったら下記 2 行だけなんですよね。

curl -c ./cookie.txt -d "data[User][username]=finger" -d "data[User][password]=ease" "https://xxxxxx.xxx/login"
curl -b ./cookie.txt "https://xxxxxx.xxx/target"

【EASE】Laravel で Chatwork 通知機能を実装してみる【Ansible】

こんにちは。
先日に Laravel で作った Ansible の管理ツールですが、プロビジョニング時に通知を行う機能が欲しいなー、と。
Slack でもよいのですが、会社で主に使っている Chatwork に通知してみます。

【Ansible】playbookをWEB上で管理/実行できるツールを作ってみた【Laravel】

事前準備

Chatwork の API トークンが必要です。
https://developer.chatwork.com/ja/

環境変数(.env)から API トークンと、通知先エンドポイントを読めるようにしておきます。

config/chatwork.php 
==========================================
<?php

# Chatworkのtokenを環境変数から取得
return [
    'cw-token' => env('CW_TOKEN'),
    'cw-endpoint' => env('CW_ENDPOINT'),
];

メソッド

Laravel では Guzzle HTTP クライアントで簡単に http リクエストを発行できるようです。
ヘッダに API トークン、ボディに通知内容のメッセージを指定するんですが、URL エンコードする必要があるので、リクエストを作成する前に asForm メソッドを呼び出します。
※プロビジョニング実行時、完了時で合計 2 つのメソッドを作成します。

use Illuminate\Support\Facades\Http;

    /**
     * 実行時 Chatwork 通知メソッド
     */
    public static function notify2ChatworkStart($user){
        $token = config('chatwork.cw-token');
        $endpoint = config('chatwork.cw-endpoint');
        $date = date("Y/m/d H:i:s");

        $message = "[info][title]EASE Ansible MG started provisioning at " . $date . "[/title]\n" . " by " . $user->name . "[/info]";
        $response = Http::asForm()->withHeaders([
            'X-ChatWorkToken' => $token,
        ])->post($endpoint, [
            'body' => $message
        ]);
        return $response;
    }

    /**
     * 終了時 Chatwork 通知メソッド
     */
    public static function notify2ChatworkEnd($ansible_output){
        $token = config('chatwork.cw-token');
        $endpoint = config('chatwork.cw-endpoint');
        $date = date("Y/m/d H:i:s");

        $message = "[info][title]EASE Ansible MG ended provisioning at " . $date . "[/title]\n" .  $ansible_output . "[/info]";
        $response = Http::asForm()->withHeaders([
            'X-ChatWorkToken' => $token,
        ])->post($endpoint, [
            'body' => $message
        ]);
        return $response;
    }

使い方

環境変数が設定されている時にメソッドを呼び出すだけです。
とてもお手軽。

# Chatworkへの通知(実行時)
if (config('chatwork.cw-token')){ Playbook::notify2ChatworkStart($user); };

# Chatworkへの通知(完了時)
if (config('chatwork.cw-token')){ Playbook::notify2ChatworkEnd($ansible_output[count($ansible_output)-2]); };  

【Ansible】Playbook を WEB で管理できるツールを作ってみた【Laravel】

こんにちは。
Ansible の Playbook 管理ツールといえば、 Ansible Tower / AWX / Ansible Semaphore 等々がございます。
これらを業務フローに組み込めないかな、と試してみたのですが、いずれもあまりピンとこず。

ないなら自分で作ってしまえと思い、年末の勢いでツールを作ってみました。
https://github.com/snkk1210/ease

実現したかったこと

ツールを作成するに辺り、実現したかったポイントは下記 7 点です。

・WEB ブラウザ上で Playbook が管理できる。
・WEB ブラウザ上で Playbook に定義された処理を実行できる。
・処理実行 ( プロビジョニング ) 履歴を保持できる。
・既に存在する Playbook ( リポジトリ ) の資産を流用できる。
・ユーザ毎に細かな権限設定が可能であり、組織的に管理できる。
・プロビジョニング時に公開鍵認証/パスワード認証の両方が使用できる。
・外部ファイルをアップロードし、処理実行時に利用することができる。

社内でのユースケースとしては、下記のようなことを実現したく実装しました。

だいたいの使い方

1. プロビジョニング対象への接続認証を登録

サイドメニューから「Make Auth」を選択し、認証名、パスワード、秘密鍵を登録

2. Playbook を作成

サイドメニューから「Make Playbook」を選択し、下記項目をそれぞれ登録

・Playbook
→ Playbook の名前

・repository
→ Playbookに使うリポジトリ名

・認証
→ 作成した接続認証

・private_key
→ 認証に使う秘密鍵
※ここで設定した秘密鍵情報が優先される(空にすると接続認証の情報を使用)

・inventory
→ インベントリファイル

・vars
→ 変数ファイル

・main
→ レポジトリに存在する role を include する Playbook

3. Playbook を選択

サイドメニューから「Playbooks」を選択し、一覧の中から使用する Playbook の「Run」を選択

4. Playbookを実行

セレクトボックスから「鍵認証」「パスワード認証」を選択して、「ドライラン」または「実行」を選択

権限周り

実装する権限は下記 3 ロールに絞りました。
※ もっと細かく調整できるように改修したいですね。

admin → 全てのリソースの操作が可能/ユーザの作成が可能
read-only → 全てのリソースの「閲覧」のみ可能
他ユーザ → 自分で作成したリソースのみ操作が可能

終わりに

余力があれば、アカウント権限操作周りの機能と、WEB ベースでのリポジトリ追加機能を実装する予定です。
始めて Laravel 使いましたが、こんな感じに実装できるんですね。
色々と勉強になりました。