Jenkins + Selenium + Chrome で テストを実行する CI/CD 環境 を作ってみる。

こんにちは。
最近、WSL に Selenium をインストールして遊んでいました。
そこで、ふと思ったのですが、これを CI/CD パイプラインに組み込んだら面白いのではないかと考えました。
Jenkinsの勉強も兼ねて、環境を構築してみることにします。
具体的には、以下のようなパイプラインを作成します。

1. サーバにコンテンツをデプロイする。
2. デプロイ後に Selenium でテストを行う。
3. テスト結果を Slack に通知する。

この一連の流れを自動化し、効率的に運用できる環境を目指します。

Jenkins 構築

CentOS7 に Jenkins / Selenium / Chrome のパッケージを導入します。

yum install wget unzip git
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum install epel-release
yum install java-11-openjdk-devel
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
yum install jenkins
systemctl start jenkins.service
systemctl enable jenkins.service

cat << "EOF" >> /etc/yum.repos.d/google.chrome.repo
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
EOF

yum install xorg-x11-server-Xvfb
yum install google-chrome-stable
yum install ipa-gothic-fonts ipa-mincho-fonts ipa-pgothic-fonts ipa-pmincho-fonts

yum install python3
pip3 install selenium

cd /usr/local/src/
wget https://chromedriver.storage.googleapis.com/93.0.4577.15/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/

Jenkins の UI から Xvfb / Slack Notification のプラグインを導入します。
※ Slack 側で Jenkins CI を予め追加しておいてください。
Jenkinsの管理 > プラグインの管理 > 利用可能 > Xvfb > Download now and install after

Jenkinsの管理 > Global Tool Configuration > Xvfb installation追加 > Name に Xvfb を入力 > Save

Jenkinsの管理 > プラグインの管理 > 利用可能 > Slack Notification > Download now and install after

Jenkinsの管理 > システムの設定 > Slack > Workspace に チームサブドメインを入力

Jenkinsの管理 > システムの設定 > Slack > Credential 追加 > Jenkins > 種類 Secret text 

Secret : インテグレーション用トークン認証情報 ID
ID : 任意

> 保存

デプロイジョブの作成

デプロイはシンプルに、ターゲットサーバに SSH 接続して develop ブランチを git pull するだけとします。
Jenkins は jenkins ユーザとして実行されるため、ホームディレクトリ配下にターゲットサーバに設定した公開鍵と対になる秘密鍵を配置しておきます。

mkdir /var/lib/jenkins/.ssh
vi /var/lib/jenkins/.ssh/id_rsa
chown -R jenkins:jenkins /var/lib/jenkins/.ssh
chmod 700 /var/lib/jenkins/.ssh
chmod 600 /var/lib/jenkins/.ssh/id_rsa

新規ジョブ作成 > フリースタイル・プロジェクトのビルド

から

ビルド手順の追加 > シェルの実行
で下記のワンライナーを実行するだけです。

ssh -oStrictHostKeyChecking=no target@xxx.xxx.xxx.xxx "cd /path/to/hoge; git pull origin develop"

ビルド後の処理の追加 > 他のプロジェクトのビルド
で 後述の テストジョブを指定します。

テストジョブの作成

Jenkins サーバの適当なディレクトリに Selenium のスクリプトを配置しておきます。
※ 今回は個人的に作った以下 WEB ツール上で、適当に画面を偏移するスクリプトを用意しました。
https://github.com/snkk1210/ease

#!/usr/bin/python3

import time
import datetime
from selenium import webdriver

URL='http://xxx.xxx.xxx.xxx'
USER="xxx@localhost"
PASSWD="xxx"

def clickHref(link):
    driver.find_element_by_link_text(link).click()
    time.sleep(1)
    return 0

def clickBtn(xpath):
    driver.find_element_by_xpath(xpath).click()
    time.sleep(1)
    return 0

def inputForm(element, input):
    driver.find_element_by_name(element).send_keys(input)
    time.sleep(1)
    return 0

def dialogboxThrough():
    driver.switch_to_alert().accept()
    time.sleep(1)
    return 0

driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver')

driver.get(URL)

# login check

inputForm("email", USER)

inputForm("password", PASSWD)

clickBtn("//button[@class='btn btn-block btn-flat btn-primary']")

# sidemenu check

side_menus = ['Playbooks','Make Playbook','Authentications','Make Auth','Archives','Upload Files','Profile','Members']
for side_menu in side_menus:
    clickHref(side_menu)

# make playbook check

clickHref("Make Playbook")

inputForm("name", "test-playbook" + str(time.time()))

inputForm("private_key", "aaa")

inputForm("inventory", "bbb")

clickBtn("//input[@class='btn btn-success']")

# edit playbook check

clickHref("Playbooks")

clickBtn("//input[@class='btn btn-success']")

inputForm("private_key", "aaa")

inputForm("inventory", "bbb")

clickBtn("//input[@class='btn btn-success']")

# archive playbook check

clickHref("Playbooks")

clickBtn("//input[@class='btn btn-warning']")

# delete playbook check

clickHref("Archives")

clickBtn("//input[@class='btn btn-danger']")

dialogboxThrough()

# exit

time.sleep(5)
driver.quit()

新規ジョブ作成 > フリースタイル・プロジェクトのビルド

から

ビルド環境 > Start Xvfb before the build, and shut it down after.
に チェックを入れます。

ビルド手順の追加 > シェルの実行
で上記のスクリプトを実行するように指定してあげるだけで OK です。

ビルド後の処理の追加 > Slack Notifications
を設定しておけば、ジョブの実行結果を Slack に通知することが可能です。

【jq】AWS WAF で Block されたリクエストの一覧を CSV に整形して出力してみる

こんにちは。
AWS WAF により Block されたリクエストの一覧を月一でリスト化する必要がございました。
最初は API を叩いて Sample request を取得し、その後にログ解析を行おうと思ったのですが、どうも、この API には 5000req/3h の制限があるようで断念…。
調べてみると Kinesis Firehose を経由して S3 に JSON 形式で WAF のログを出力できるようです。
ついては出力された S3 のログを引っ張って jq でリスト化すればいいや、となりました。

シェルスクリプト

cron で月末にスクリプトが実行されるよう設定しておけば、あとは放置で OK ですね。

#!/bin/bash

WORK_DIR=""
THIS_YEAR=`date "+%Y"`
THIS_MONTH=`date "+%m"`
S3_BUCKET=""
PROFILE=""
OUTPUTDIR=""
OUTPUT=${OUTPUTDIR}block_requests-`date +'%FT%R'`.csv

# 作業ディレクトリの作成
mkdir -p ${WORK_DIR}/${THIS_YEAR}/${THIS_MONTH}

# S3 からログのダウンロード
aws s3 cp s3://${S3_BUCKET}/${THIS_YEAR}/${THIS_MONTH}/ ${WORK_DIR}/${THIS_YEAR}/${THIS_MONTH} --recursive --profile=${PROFILE}

# csv のフィールド名を出力
echo "Request.ClientIP,TerminatingRuleId,Request.Country,Request.URI,Request.httpVersion,Request.Method,Other" >> ${OUTPUT}

# ログの一覧を取得
SERCH_FILES=`find ${WORK_DIR}/${THIS_YEAR}/${THIS_MONTH} -type f -name aws-waf-logs*`

for SERCH_FILE in ${SERCH_FILES}
do
    # json -> csv の変換 / パース / 出力
    cat ${SERCH_FILE} | jq -r ' select( .action == "BLOCK") | [.httpRequest.clientIp, .terminatingRuleId, .httpRequest.country, .httpRequest.uri, .httpRequest.httpVersion, .httpRequest.httpMethod, "#HeadersName", .httpRequest.headers[].name, "#HeadersValue",.httpRequest.headers[].value, "#RuleGroup", .ruleGroupList[].ruleGroupId, "#RuleMatch", .ruleGroupList[].terminatingRule.ruleId, "#RuleAction", .ruleGroupList[].terminatingRule.action] |@csv' \
    >> ${OUTPUT}
done

jq って便利ですね。

【C】ソケットAPIを用いたデータロガープログラムを書いてみる【socket】

こんにちは。
TCP/IP でのソケットを用いたデータ通信の仕組みに関して改めて勉強してみたので、自分自身の理解のために簡単なデータロガープログラムを書いてみました。
※ いつも通りエラーハンドリングは 無視 省力です。

client.c

まずはクライアント側ですね。
socket() でソケットを作成した後に、connect() を使えばコネクションが張れるようです。
後は生成したソケットを使って、send() でサーバ側にデータを送るだけです。
送信するデータは fgets() で標準入力から取得します。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void){
    int sockfd;
    char mes[64];
    struct sockaddr_in server;

    server.sin_family = AF_INET;
    server.sin_port = htons(8888);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    connect(sockfd, (struct sockaddr *)&server, sizeof(server));

    printf("INPUT:");
    fgets(mes, sizeof(mes), stdin);

    send(sockfd, mes, 64, 0);

    close(sockfd);

    return 0;
}

接続先サーバの情報は sockaddr_in 構造体を使って socket に紐づけます。
※ sockaddr にキャストする必要アリです。
例では localhost の 8888 ポートに接続しています。

server.c

次はサーバ側ですね。
サーバは bind() を使って ソケットに IPアドレス/ポート番号を紐づけ、listen() で割り当てたポート番号に接続を作成できることをシステムに伝えます。
※ 例では最大5つの接続を受け持ちます。( 後段の処理が完了するまで最大5つまでの接続要求がキューに入ります )
実際のデータの送受信にはこのソケットは用いず、accept() を使ってデータ送受信用のソケットを生成して処理を委任します。
後は recv() を使ってデータを受信し、ファイルにデータを書き込み データ送受信用のソケットをクローズします。
※ 36行目には到達しないです。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void){
    int sock, sockfd, sock_size;
    char mes[64];
    struct sockaddr_in server;
    struct sockaddr_in client;
    FILE *file;

    server.sin_family = AF_INET;
    server.sin_port = htons(8888);
    server.sin_addr.s_addr = INADDR_ANY;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bind(sockfd, (struct sockaddr *)&server, sizeof(server));

    listen(sockfd, 5);

    while(1){
        sock_size = sizeof(client);
        sock = accept(sockfd, (struct sockaddr *)&client, &sock_size);

        recv(sock, mes, 64, 0);
        file = fopen("loger.txt", "a");
        fprintf(file, mes);
        fclose(file);
        
        close(sock);
    }
    close(sockfd);
    return 0;
}

クライアントと同じく、受け持つIPアドレス/ポート番号を sockaddr_in 構造体を使って socket に紐づけます。
こちらも sockaddr にキャストする必要アリです。
※ INADDR_ANY は全ての接続元( 0.0.0.0 )を表しています。

まとめると下記の形になりますね。

1.socket() を実行して TCPソケットを作成する。
2.bind() を実行してソケットにポート番号(IPアドレスも)を割り当てる。
3.listen() を実行し割り当てたポート番号へ接続を作成できることをシステムに伝える。
4.以下繰り返し
・接続要求を受け取るたびに、accept() を呼び出して新規ソケットを取得
・作成したソケットを介してクライアント(接続要求元)とやり取り
・close() でクライアントとの接続をクローズ

【PHP】Chatwork 通知用の Proxy API を作ってみる

こんにちは。
とある案件でお客様 Zabbix サーバから Chatwork にアラートを通知する必要がございました。
通常であれば、Chatwork の API を叩くスクリプトを用意すればよいのですが、お客様管理の Zabbix サーバですので、セキュリティ的な要因から Chatwork の APIトークンを使うことができません。
ついては、自社サーバに API トークンを配置した Proxy API を用意してみます。

コード

前回は cURL関数 を使ったので、今回は file_get_contents を使ってみます。

<?php
/*
$api_key = "";
$password = "";
*/
include('../../.env');

// リクエストの body を取得
$json = file_get_contents("php://input");
$contents = json_decode($json, true);

// POST されたデータが正しいか判定
if (isset($contents['cw_endpoint']) && is_numeric($contents['cw_endpoint']) && isset($contents['message']) && isset($contents['auth_id'])){
    $cw_endpoint = $contents['cw_endpoint'];
    $message = $contents['message'];
    $auth_id = $contents['auth_id'];
    } else {
        echo "Bad Request.\n";
        http_response_code(400);
        exit();
    }

// 認証情報が正しいか判定 
if ($password !== $auth_id){
    echo "Forbidden.\n";
    http_response_code(403);
    exit();
}

// HTTP ヘッダ
$header = array(
        "Content-Type: application/x-www-form-urlencoded",
        "X-ChatWorkToken: $api_key",
);

// HTTP リクエスト
$context = array(
        "http" => array(
                "method"  => "POST",
                "header"  => implode("\r\n", $header),
                "content" => http_build_query(array(
                    'body' => $message,
                )),
        )
);

// ChatWork API にリクエスト送信
$scc = stream_context_create($context);
$url = "https://api.chatwork.com/v1/rooms/$cw_endpoint/messages";
$res = file_get_contents($url, false, $scc);
echo $res . "\n";

APIトークン と 認証情報は外部より参照できないように、ドキュメントルートより上位に配置しておきます。

<?php
$api_key = "";
$password = "";
?>

使用例

POSTメソッド で HTTPリクエストを投げれば OK です。

・パラメータ
cw_endpoint:通知するルームID
message:通知内容
auth_id:xxxxxx

・curl の例

curl -X POST -d '{"cw_endpoint":"xxxxxxx", "message":"これはテスト", "auth_id":"xxxxxxxxxxxxxx"}' https://xxx.xxx.xxx.xxx/api/proxy.php

【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 使いましたが、こんな感じに実装できるんですね。
色々と勉強になりました。

【サーバレス】Lambda + API Gateway + Flask で Web ツールを作ってみる【AWS】

こんにちは。
仕事でサーバレス環境を扱うことがありまして、勉強がてら少し触ってみようと思ったんですね。
先日に SSL/TLS 証明書の整合性チェックツールを Flask で作ったんで、こちらをサーバレス環境で動かしてみます。
https://github.com/keisukesanuki/certificate_verify_flask_lambda

zappa

コードのデプロイ用に zappa というツールがあるそうです。。
下記手順で簡単に Lambda + API Gateway の環境構築とアプリケーションのデプロイが完了するようです。

# 必要なパッケージの導入
yum groupinstall "Development tools" -y
yum install zlib-devel openssl-devel sqlite-devel -y

# pyenvの導入
git clone https://github.com/pyenv/pyenv.git ~/.pyenv
echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

# pyenv-virtualenvの導入
git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile
exec $SHELL -l

# 3.6系のpythonを導入
pyenv install 3.6.5
pyenv global 3.6.5

# awscliの導入/設定
pip install -U pip
pip install awscli
aws configure

# ソースの取得
pyenv virtualenv 3.6.5 lambda
git clone https://github.com/keisukesanuki/certificate_verify_flask_lambda.git 
cd certificate_verify_flask_lambda
pyenv local lambda

# モジュールの導入
pip install -U pip
pip install flask
pip install zappa

# lambdaにデプロイ
zappa init
===========================================================================
What do you want to call this environment (default 'dev'): certificateCheck
===========================================================================
zappa deploy

リストファイルを分割して複数のサーバに配布するスクリプト

JMeter での負荷試験時に slave の個数分だけ CSVファイルを分割して、それぞれの slave サーバに scp で転送したかったのです。
そんな背景で取り急ぎ作成したよくわからない何かです。

スクリプトみたいな何か

#!/bin/bash

############################################
# 第一引数に配布先マシンのIPアドレス(9つまで)
# 第二引数に分割して配布するリストファイル
############################################

# ファイルの転送先ディレクトリ
DIR=/var/tmp/dlist.txt
# 接続ユーザ
USER=vagrant
# 接続に使用する秘密鍵
SECRET_KEY=/home/vagrant/.ssh/id_rsa

# 引数の数をチェック
if [ $# != 2 ]; then
    echo "引数の数が不正です"
    exit 1
fi

# 配布先マシンの数量を取得
TARGET_COLUMN=`cat $1 | wc -l`
# リストファイルの行数を取得
LIST_COLUMN=`cat $2 | wc -l`
# 1ターゲット辺りのリスト数を取得
ONE_TARGET_NUM=$[LIST_COLUMN / TARGET_COLUMN]

# 配布先マシンのIPアドレスが9つ以上あるかチェック
if [ $TARGET_COLUMN -gt 9 ]; then
    echo "配布先IPアドレスは9つまでしか設定できません"
    exit 1
fi

# 配布先マシンの数量分だけファイルを分割する
split -l $ONE_TARGET_NUM -a 1 --numeric-suffixes=1 $2 list.

# 分割したファイルを転送する
while read VAL;
do
  NUM=$((NUM+1))
  scp -i $SECRET_KEY ./list.$NUM $USER@$VAL:$DIR

  if [ $? -ne 0 ]; then
    echo "scp コマンドが正常に終了しませんでした"
    exit 1
  fi

  # 分割したファイルを削除
  rm -f ./list.$NUM
done < $1

スクリプトの第一引数に下記のような形で転送先サーバの IP アドレスを記載したファイルを渡せば OK です。
※ 第二引数に分割したいリストファイルを指定してください。
192.168.33.11
192.168.33.12
192.168.33.13
192.168.33.14
192.168.33.15
192.168.33.16

【Lambda】CloudWatch の通知を Lambda で Chatwork に飛ばしてみる【Python】

こんにちは。
表題の通り、CloudWatch の Alarm 通知を Chatwork に通知してみます。
ランタイムは前回と同じく python 3.7 です。
ROOMNO にメッセージを通知するルームナンバ、 TOKEN に ChatWorkToken を定義すれば動きます。

import boto3
import json
import logging
import os
import urllib
 
from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

# Chatwork のルームナンバ定義
ROOMNO = 'xxxxxxxxxxxxxxxx'
 
# WEB_HOOKURL
URL = f'https://api.chatwork.com/v2/rooms/{ROOMNO}/messages'

# ChatWorkToken 定義
TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

logger = logging.getLogger()
logger.setLevel(logging.INFO)
 
 
def lambda_handler(event, context):
    logger.info("Event: " + str(event))

    # メッセージ取得
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info("Message: " + str(message))
 
    alarm_name = message['AlarmName']
    new_state = message['NewStateValue']
    reason = message['NewStateReason']

    # ヘッダ情報
    headers = {
        'X-ChatWorkToken': TOKEN,
    }  
 
    # 通知内容
    sns_message = {
        'body': "%s state is now %s: %s" % (alarm_name, new_state, reason),
    }
    
    # エンコード
    msns_message = urllib.parse.urlencode(sns_message)
    msns_message = msns_message.encode('utf-8') 
    
    # リクエスト発行
    req = Request(URL, data=msns_message, headers=headers)
    with urlopen(req) as res:
        result = json.loads(res.read().decode("utf-8"))