信頼はずっと、挑戦はもっと。

お問い合わせ
TEL:03-3496-3888

BLOG コアテックの社員ブログ (毎週月曜~金曜更新中)

LIST OF ARTICLES

記事一覧

  • テクログ

    slackコマンドっぽいことをchatworkでしたい!

    そろそろ夏も終わりですね。はやく涼しくなってほしいですね。そういう気持ちからslackコマンドっぽいことをchatworkでしたい!ということになりました。-----------------------◆本当の経緯もとからslackでのchatops的なものは結構やっていたんですが、弊社slackだけではなくchatworkも使うんですね(ぼやかしで、chatworkもWebhookがあって、slackよりも機能や設定できることは少ないけど、なんとか似たことができそうだな、となったので作りました。-----------------------◆構成chatwork→APIGateway→Tokenやコマンドを検証し、振り分けるLambdaFunction→(SNS)→コマンドの実態LambdaFunction+chatworkへ結果返答わかりやすくするためTokenやコマンドを検証し、振り分けるLambdaFunction を [振り分けLambda]コマンドの実態Lambda を [実態Lambda]とします-----------------------◆設定を簡単に解説細かい設定は書きませんが、いろいろ試行錯誤するのも経験のうちですよね。(1)APIGatewayを作成chatworkのWebhookが叩く入り口です。URLがあればひとまずOKです。パス設定とかはおまかせします。(2)[振り分けLambda]の殻を作成APIGatewayの先のLambdaを作成します。(3)APIGatewayを設定(1)と(2)をつなぎまーす叩くとレスポンスが来るだけ確認しておくのもいいですね。(4)chatworkのWebhook設定chatworkにボットアカウントを作り、そのアカウントでWebhookの設定をします参考:https://help.chatwork.com/hc/ja/articles/115000169541-Webhook%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8BURLはAPIGatewayのURLで、イベントはルームイベントにしました。(なぜなら、目に見えないところではなく、その部屋だけで使ってほしいからです。)メッセージ作成のみ、にしました。(5)[振り分けLambda]を実装ソース!だよ!何もライブラリが要らないってのを重視。なんか貼ったら改行があれなのでまあそこはいいかんじにしてください。何かあってもこちらはサンプルですのでそこはいつものとおりご了承ください。見直すと入ってきた文字整形とか無理矢理感。chatworkはクライアントごとにメッセージの形式が微妙に違ったりしてね…必要な環境変数はCHATWORK_API_TOKEN (KMSで暗号化してある、postに使うchatworkのTokenWEBHOOK_TOKEN (渡ってきたchatworkのメッセージが正しいものかを判定するためのTokenです。# -*- coding: utf-8 -*- # chatwork commanderの入り口 import boto3 import json import logging import os import sys from datetime import datetime from botocore.exceptions import ClientError import copy import urllib.request, urllib.error from base64 import b64decode, b64encode import hmac import hashlib valid_command_list = ['こまんど1','こまんど2','こまんど3'] to_str = "[To:1111111111]" usage_str = "【コマンド一覧】\n" + \ "※それぞれのコマンドの使い方については[コマンド usage]を打って確認してください\n\n" + \ "こまんど1\n" + \ "こまんど2\n" + \ "こまんど3\n" SNS_ARN_PREFIX = "arn:aws:sns:ap-northeast-1:11111111111:chat_command_" #### #メイン #### def lambda_handler(event, context):   #先にイベントは取っておく(判定とかに使うから   eventjson = json.loads(json.dumps(event))   bodyjson = json.loads(eventjson['body'])   room_id    = str(bodyjson['webhook_event']['room_id'])   message_id = str(bodyjson['webhook_event']['message_id'])   body      = str(bodyjson['webhook_event']['body'])   account_id = str(bodyjson['webhook_event']['account_id'])   #まず   #[To:1111111111]   #がないなら無視(全メッセージ流れてくるから   if not to_str in body:     print('not me.END')     return {'statusCode': 200,'body': json.dumps('not me.END')}   #毎回kms使うのがやだからここでやってます   ENCRYPTED_WEBHOOK_TOKEN = os.environ['WEBHOOK_TOKEN']   WEBHOOK_TOKEN = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_WEBHOOK_TOKEN))['Plaintext']   ENCRYPTED_CHATWORK_API_TOKEN = os.environ['CHATWORK_API_TOKEN']   CHATWORK_API_TOKEN = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_CHATWORK_API_TOKEN))['Plaintext']   #Tokenの確認   if check_request(event, WEBHOOK_TOKEN) == True:     print("Token OK")     #body整形     command_text = body.replace('\n', '').replace('[To:1111111]', '').replace('aaaaa さん', '').replace('aaaaaさん', '').strip()     command_args = command_text.split(" ")     #コマンドの確認     command = command_args[0]     nextStatus = checkCommandStrAndMakeNextStatus(command, room_id, account_id, message_id, command_text, CHATWORK_API_TOKEN)     #nextStatusがTrue(続行)の場合、次のlambdaをSNSで呼ぶ     #続行ではない場合は特に何もせずreturnして終わり     if nextStatus == True:       message = {"room_id"   : room_id,             "command_args": command_args,             "account_id" : account_id,             "message_id" : message_id       }       #SNS_ARN_PREFIX+command がARNになるようになっている       sendSNS(message, SNS_ARN_PREFIX+command)     return {       'statusCode': 200,       'body': json.dumps('OK')     }   else:     print("Token NG")     return {       'statusCode': 403,       'body': json.dumps('NG')     } ##----------------------- def sendSNS(message, sns_TOPIC):   sns_client = boto3.client(     'sns'   )   #メッセージ整形   message=json.dumps(message)   message=json.dumps({'default': message, 'lambda': message})   response = sns_client.publish(     TopicArn=sns_TOPIC,     Subject='/lambda',     MessageStructure='json',     Message=message   )   print(response) #### #コマンドの内容を確認してchatwork通知 #また、今後の続行ステータスを返す #### def checkCommandStrAndMakeNextStatus(command, room_id, account_id, message_id, command_text, CHATWORK_API_TOKEN):   if command == 'usage':     postChatwork(usage_str,room_id, CHATWORK_API_TOKEN)     return False#終了   else:     if not command in valid_command_list:       postChatwork("対象コマンドがありません。\n"+usage_str,room_id, CHATWORK_API_TOKEN)       return False#終了     else:       #chatworkに受付状況を通知       postChatwork(         makeReplyMessage(account_id, room_id, message_id, command_text),         room_id,         CHATWORK_API_TOKEN       )       return True#続行 #### #メッセージをつけるだけ #### def makeReplyMessage(account_id, room_id, message_id, command_text):   return "コマンド:" + command_text + "\nを受け付けました。(コマンドの実態に渡しました)\nお待ち下さい。" #### #chatworkに通知 #### def postChatwork(message, room_id, CHATWORK_API_TOKEN):   print("in postChatwork")   END_POINT_BASE = "https://api.chatwork.com/v2"   END_POINT   = "/rooms/"   ACTION     = "/messages"   headers = { 'X-ChatWorkToken': CHATWORK_API_TOKEN }   data = { 'body': message }   data = urllib.parse.urlencode(data)   data = data.encode('utf-8')   # リクエストの生成と送信   post_message_url = END_POINT_BASE + END_POINT + room_id + ACTION   request = urllib.request.Request(post_message_url, data=data, method="POST", headers=headers)   with urllib.request.urlopen(request) as response:     response_body = response.read().decode("utf-8")     print(response_body) #参考 #https://qiita.com/ikedaosushi/items/b01183f207ea1db6a8d3 def check_request(event, WEBHOOK_TOKEN):   webhook_sig = event['headers']['X-ChatWorkWebhookSignature'].encode('utf-8')   sig = build_sig(event['body'], WEBHOOK_TOKEN)   is_valid = webhook_sig == sig   return is_valid def build_sig(body, WEBHOOK_TOKEN):   token_decode = b64decode(WEBHOOK_TOKEN)   sig = hmac.new(token_decode, body.encode('utf-8'), hashlib.sha256).digest()   sig_encoded = b64encode(sig)   return sig_encoded だいたいでいえば・メッセージはルームのものがすべて来るので、処理対象メッセージなのかを判定・tokenの確認・コマンドの確認・受け付けたことを返答・SNSで次のLambdaを呼ぶ(このSNSを一定の形式に統一することによって、後ろのコマンドを増やしやすくなってます。(6)実態Lambdaを呼ぶためのSNSを作るarn:aws:sns:ap-northeast-1:11111111111:chat_command_ ってなっているようにSNSをまず作りますたとえばmake_EC2みたいなコマンドだった場合、SNS名はchat_command_make_EC2になりますね。(7)[実態Lambda]を作るここは自分でつくってね!SNSを受け取って、分解して、処理をすればいいです。終わったらchatworkに通知したり、いろいろ内部でやればいいですね。夢が広がる。(8)SNSの先に[実態Lambda]を追加まあ追加するだけですあとはchatworkでボットにはなしかけたりしてテストをしてみよう!-----------------------コマンドを増やすときも、許可するコマンドを追加して、SNS作成コマンド実態Lambda作成だけでぽんぽんふえるよ!ルームを分けることによって、重要度ごとに使える人を分けられたりもします。以上夏休みの自由研究にどうぞ。もうおわってますか。-----------------------<以下はただの一覧用の画像でーす>
  • テクログ

    ブラウザテストフレームワークの5月でした

    ブラウザテストフレームワーク、使ってますか!単体テストもいいけど、やっぱりユーザが使って実際ちゃんと動作してるの?ってのが気になりますよね。最近はjsでいろいろやることも多いし、関連するところが動かなくなったり。(jsの単体テストやれって話もありますが)で、有償無償問わず、いろいろと見てみました。結構網羅したり、試したりするだけでもそれなりにかかったので、なんとなく一覧的に。・いわゆるツールでの自動テスト系teststudioRanorexAutifyTestCompleteUnified Functional TestingROBOWAREuipathkatalon studioimacros・いわゆるブラウザテストフレームワーク(E2Eテスト用)Seleniumを直使用CodeceptionCodeceptJSNightwatch.jsWebdriverIOScrapywatirAppiumSelenideGebおよびSpockCapybaraSplinterCasperJSSST (selenium-simple-test)重要視した点としては、パッとつくれて、パッと動かせる。なんだこれ、どうやるんだ、みたいなのはなし。……ということで、有償の自動テストツール系について、可能なものは体験版を入れてちょっと動かしたりしました。「ちょっと」なのはあえてちょっとだけやって、それでもできないのならば簡単じゃない!ということでした。もちろんデモや動画をみてると、「なんかすごいことやってるし、なんでもできそう…」となりますし、実際理解すればなんでもできるのかもしれません。でも少しだけいじっただけでは全く思ったとおりに動かないんですよね。というわけでツール系はなくなり、ブラウザテストフレームワークの検証となりました。テストコード書かないといけない、というのは確かに手軽とはいえないですが、サンプルがあればあとはその改良をつづけていけばなんとかなる、という思惑です。現状のサポート具合、活発さ、書きやすさ…などからCodeceptionCodeceptJSが残り、mac,PCでのブラウザテストはクリア。実機もやりたい、ということでAppium連携をしたり…ということをやっていましたよ。実機動作は結構コツが必要だったり、iPhoneだとやりたいことがどうしてもできない部分があったり…となりましたが、それ以外は結構思ったとおりのテストができましたので、毎回確認しないといけない動作がある、とか、そういった場合には役に立つのではないでしょうか。一個小ネタでいえば、テストをAWS Lambdaに連携させて、外への影響を確認したりする、ということもやってみました。純粋なE2Eテストの範疇からは外れるかとは思いますが、やはりどうしても確認したい内容もありますので、そういうものもいかがでしょう。ちなみに最終的に残ったのはCodeceptJS+Appiumでした!(以下イメージ画像
  • テクログ

    Deployerでも動的にデプロイするホストを変更したい!

    ↑ってなりますよね?なるなるーホストが固定だと、deploy.phpにホストを書いて終わり、ってなるんですけど、AWSなどで今動いてるホストがコロコロ変わるとしたらdeploy.phpには固定で書けないですね。うーんこまったどうしよう。そんなときはこれ!hosts.yml〜くわしくはこちら!https://deployer.org/docs/hosts.htmlhosts.ymlにしたがってデプロイするよってしておけば、hosts.ymlを事前に生成すれば動的にできるんだ!(deploy.phpには inventory('hosts.yml');って書けば使われるよ。まあリンク先↑に書いてある。)なんだって!すごいね!はいソース の 例。pythonのほうが楽なんだけどいじりやすいようにphpにしましたですよ?雛形(SOURCE_hosts.yml)をつくっておいて、それのIPとインスタンスIDをリプレースしてるっす。わかるっすよね。雛形はじぶんでつくってね!(リンク先参照とかテストとかして)対象EC2はタグで絞り込んだりとかもしてます。これを使ってなにかあっても責任は全く取れませんなので十分に検証してね!hosts.yml生成するだけですけど。これをjenkinsに呼ばせるなら、失敗時はexit(1);とかすれば進まないのでは。ではいいかんじのDeployerライフを。<?php require 'aws_v3.phar'; use Aws\Ec2\Ec2Client as Ec2Client; use Aws\Exception\AwsException; echo 'generate_autoscale_yml START' . PHP_EOL . PHP_EOL; /**設定箇所 開始**/ //認証情報 $auth = [         "key"   => "ああああ",         "secret" => "いいいい",         "region" => "ap-northeast-1",         "version" => "2016-11-15", ]; //タグ、AutoScaleGroupの値 $AutoScaleGroup_value = 'もがもが'; //hosts.ymlの雛形となるものを読んでおく $source_hosts_yml_string = file_get_contents('./SOURCE_hosts.yml', FILE_USE_INCLUDE_PATH); //結果ファイル名 $hosts_yml_filename = './hosts.yml'; /**設定箇所 終了**/ //hosts.ymlに結果として書き出す文字列の殻 $hosts_yml_string = ''; try{     //動いている(running)、タグが該当のインスタンスを取得     $ec2client = Ec2Client::factory($auth);     $result = $ec2client->describeInstances([             'Filters' => [                     [                             'Name' => 'tag:AutoScaleGroup',                             'Values' => [$AutoScaleGroup_value],                     ],                     [                             'Name' => 'instance-state-name',                             'Values' => ['running'],                     ],             ],             'MaxResults' => 100,     ]);     //値の分解と使用     $reservations = $result['Reservations'];     foreach ($reservations as $reservation) {         $instances = $reservation['Instances'];         foreach ($instances as $instance) {             //もととなる文字のコピー             $mod_hosts_yml_string = $source_hosts_yml_string;             //置き換えに使う文字             $InstanceId = $instance['InstanceId'];             $publicip   = '';             foreach ($instance['NetworkInterfaces'] as $networkinterface) {                 $publicip = $networkinterface['Association']['PublicIp'];             }             //[INSTANCE_ID]と[PUBLIC_IP_ADDRESS]を置き換え             $mod_hosts_yml_string = str_replace('[INSTANCE_ID]', $InstanceId, $mod_hosts_yml_string);             $mod_hosts_yml_string = str_replace('[PUBLIC_IP_ADDRESS]', $publicip, $mod_hosts_yml_string);             $hosts_yml_string .= $mod_hosts_yml_string;         }     }     //書き込み     file_put_contents($hosts_yml_filename, $hosts_yml_string); } catch(AwsException $e){     var_dump($e->getMessage());     echo 'ERROR' . PHP_EOL . PHP_EOL; } echo 'generate_autoscale_yml END' . PHP_EOL . PHP_EOL;
  • テクログ

    AWSソリューションアーキテクトと不思議のAPNダンジョン

    はいどうも、今日のAPN探訪のお時間がやってきました。渋谷・新宿・梅田ダンジョンよりも高度と噂されるAPNダンジョン、ランダム生成されているのではないかというリンクの海をくぐりぬけ、我々はAWSソリューションアーキテクトの申込みを完了したのであった。。。(つづかない)-----------------------過去問サンプルをみて、「これならいけるかな?」というかんじではありましたが、対策テキストをお借りしまして見ていました。そこはかとなくAWSを使っていて感じていたことが、そういう思想で作られてるんだな、ってのがわかってなかなか面白かったです。ぼんやりと思っていたことが、外部講習などで補強される感じに近いでしょうか。いわゆる「コードだけ書けばいい」と言われることが多くなったように、それを実現するための可用性や内部的なしくみはこっち(AWS)がやるから、もっとバンバン実際に動くところ作っていけよ!というメッセージというか、そういうものだったんですね。『こまけえこたぁいいんだよ、ここは任せてお前は先にいってコードを書け!』という感じでしょうか。「チューニング?まあそれも大事かもしれないけどハードも毎回変わるし大変だろ??(Netflixはやってるってありましたけど)そんなことよりどんどんEC2の世代あげろよ!安いし30%高速だぜ!」みたいなかんじですね。まあ大体の場合が問題ない設定になってるし、と。ぽんぽん変わるのに腰をすえてじっくりチューニングしてもすぐ次が来る、って感じだと思います。間に合わん。クラウドのときは頭を切り替える、些事はほっといて大事なことをやる、ってのが大事だな、と再認識しました。-----------------------完全にポエムですがそういうときもありますよね。
  • テクログ

    410からの〜お知らせページ表示〜

    内部的に「Lambdaとはなにか会」をやったり「単体テストを普及させて幸せになりたい」なって思っていろいろやったりしてる昨今、いかがおすごしですか。さて、突然ですがステータスコード404ってのはよく見かけますが、410ってあんまりピンと来ないですよね。簡単に言うと404は意図的なのか、単に今みれないのかわからないけど、なんかページがない、410は意図的になくなりました!という、みたいな違いらしいです。参考ページ:https://www.seo-ch.jp/news/470で、特集ページなどをhttps:/サイトドメイン/hogehoge/などで作っていて、特集が終わったりして、『無くなったよ!』と伝えたい気持ちが高まった場合、どうするんでしょうか。(+で、このページはなくなったんです、というお知らせも表示したい気持ちもあります)こうします!ErrorDocument 410 /hogehoge/index.html <IfModule mod_rewrite.c>   RewriteEngine On   RewriteCond %{REQUEST_URI} ^/hogehoge/index.html$   RewriteRule ^.*$ index.html [L]   RewriteCond %{REQUEST_URI} ^/hogehoge/$   RewriteRule ^.*$ index.html [L]   RewriteRule .* - [G] </IfModule> /hogehoge/index.htmlが告知ページです。ルートの場合/またはindex.htmlへのアクセス以外は、一番下の  RewriteRule .* - [G] で410へ飛ばします。それだけだと410エラーが表示されますので、もしも410エラーのときにはindex.htmlを出す、ということを1行目でかいています。これで410を出したい気持ちを解消することができましたね!めでたしめでたし。以下は開催したLambdaとはなにか会のスライドの端っこです。見栄えのためです。
  • テクログ

    コスト配分タグを自動でチェックしたい!

    恒例のしたいシリーズです。いまつくりました。●なにがしたいの?AWSアカウント内に複数の媒体があって、どの媒体にどれだけお金がかかっているかをチェックするために、リソースにProjectタグをつけると簡単に集計できます。でも、リソースは随時増えるし、ちゃんとProjectタグついているのかわからない。なんとかしたいという気持ち。●どうやるの?LambdaでリソースにProjectタグがついているかをチェック。+Projectタグの値もチェック(ちがうのがついてたらこまるからね)チェックしない、というのも設定したい。あと、結果はSlackに通知したりしたい。●具体的には?ソースをはりますんで見てね!EC2のタグチェックの例です。サンプルですよ。・追加ライブラリslackwebいれてください。・使っている環境変数ACCOUNT_NAME AWSアカウントの名前とかをいれます。通知用です。CHECK_TAG_NAME チェックしたいタグをいれます。今回はProjectです。CHECK_TAG_VALUES カンマ区切りで、入っていていほしいタグの値をいれます。aaa.jp,bbb.jp  みたいなのですSLACK_URL SlackのWebhookURLです。・ソース(Pythonらしさはかけらもありませんがゆるしてください# -*- coding: utf-8 -*- # EC2のタグ設定チェック import boto3 import os import slackweb import sys # アカウント名 ACCOUNT_NAME   = os.environ['ACCOUNT_NAME'] # チェックするタグ名 CHECK_TAG_NAME  = os.environ['CHECK_TAG_NAME'] # ついていてほしいタグの内容(どれかならOK) CHECK_TAG_VALUES = os.environ['CHECK_TAG_VALUES'].split(",") #slackurl SLACK_URL    = os.environ['SLACK_URL'] REGION_NAME = 'ap-northeast-1' ##################################################### #本体 ##################################################### def lambda_handler(event, context):   #タグチェック   results = checkTags()   print('checkTags results:')   print(results)   checkResult_AND_Notify(results) ##################################################### # インスタンスID一覧を取得 ##################################################### def checkTags():   instance_list = []   #ページャーで全て取得   client  = boto3.client('ec2', region_name=REGION_NAME)   paginator = client.get_paginator('describe_instances')   # Filterで止まっているものは除外   page_iterator = paginator.paginate(     Filters=[     {       'Name': 'instance-state-name',       'Values': [         'running',       ]     }   ],)   #チェックしていく   for page in page_iterator:     for reservation in page["Reservations"]:       for instance in reservation["Instances"]:         instance_id = instance["InstanceId"]         #Tagsを見て、CheckProjectSettingがNOならば除外(存在しない、またはYESなら追加         check_flag       = True         project_tag_exist_flag = False         project_tag_value   = ""         name_tag_value     = ""         Tags = instance["Tags"]         for tag in Tags:           if tag['Key'] == 'CheckProjectSetting':             if tag['Value'] == 'NO':               print('CheckProjectSetting,NO:' + instance_id)               check_flag = False           if tag['Key'] == 'Project':             project_tag_exist_flag = True             project_tag_value = tag['Value']             print('Project tag_value:' + project_tag_value)           #Nameはわかりやすさのため取ります           if tag['Key'] == 'Name':             name_tag_value = tag['Value']             print('Project tag_value:' + name_tag_value)         if check_flag == True:           print('CheckProjectSetting,YES:' + instance_id)           if project_tag_exist_flag == False:             #タグ自体がない場合は即追加             instance_id_dict = {               "instance_id"    : instance_id,               "name_tag_value"  : name_tag_value,               "project_tag_value" : project_tag_value,               "reason"      : "no_project_tag"             }             instance_list.append(instance_id_dict)           else:             #タグはあった場合、中身を確認し、なかったら一覧追加             if project_tag_value not in CHECK_TAG_VALUES:               instance_id_dict = {                 "instance_id"    : instance_id,                 "name_tag_value"  : name_tag_value,                 "project_tag_value" : project_tag_value,                 "reason"      : "project_tag_not_match"               }               instance_list.append(instance_id_dict)   return instance_list ##################################################### # 漏れがあるものを通知 ##################################################### def checkResult_AND_Notify(results):   print('checkResult_AND_Notify')   exist_no_Tag_Flag = False   res_str = ''   for result in results:     exist_no_Tag_Flag = True     res_str += '&gt;  Name:' + result['name_tag_value'] + ' ID:' + result['instance_id'] + ' 理由:' + result['reason'] +'\n'   if exist_no_Tag_Flag:     title_str = "ProjectタグをつけてないEC2インスタンスがあったよ!付けてね!\n"     slack = slackweb.Slack(url=SLACK_URL)     slack.notify(text=ACCOUNT_NAME + "のProjectタグチェックをしたよ!\n\n" + title_str + res_str)   else:     print("ProjectタグをつけてないEC2インスタンスはなかったよ!") ※Tagsを見て、CheckProjectSettingがNOならば除外やったね!もうタグがあるかどうかをチェックしなくていいよ!肝心のタグ設定は場合によりますので、手動で付けてくださいね。以上!(見栄え用の画像↓)