Athenaで入れ子のjsonにクエリを投げる方法が分かりづらかったので整理する

Kinesis FirehoseでS3に置かれた圧縮したjsonファイルを、それに対してクエリを投げる、というのを検証してたのですが、Hive素人なのでスキーマの作り方もクエリの投げ方も正直あんまり良くわかってませんでした。

そこで下記を参照しながらスキーマの作成とクエリ投入をやってみて、最終的にうまくいきました。

日本語記事
https://aws.amazon.com/jp/blogs/news/create-tables-in-amazon-athena-from-nested-json-and-mappings-using-jsonserde/

元記事
https://aws.amazon.com/jp/blogs/big-data/create-tables-in-amazon-athena-from-nested-json-and-mappings-using-jsonserde/

ずーっと日本語記事を読みながらやっていたのですが、これがめちゃくちゃわかりづらい!!!
※理解度には個人差があります

多分知っている人が見たら何となくわかるんでしょうが、恐らくこれを見るのは自分みたいにあまり良く知らないので参考にしながら実際にやってみている、という層だと思います。
最終的に上手く行ってから思ったのは、前提知識がないと読むのがしんどい、ということですね…。
ただもう少し書いといてくれるだけで十分なのに…。
原文も軽く見ましたが、そっちにも書いてないのでそもそも記述されてません。

調べてもまだ中々情報が出てこない上に、クエリ投入時にエラーが出た場合もエラーメッセージが淡白すぎてどこが問題でエラーになってるのかさっぱりわからなくて悪戦苦闘してました。

そんなわけで、今後同じところで困る人が一人でも減るように、自分用メモも兼ねてハマったところについて補足をしておきたいと思います。

概要

リンク先で書いてあることの流れは大まかに下記のとおりです。

  1. FirehoseでSESの送信イベントログをS3に保存する
    送信イベントログはjson形式で、それをFirehoseでS3に保存しています。
  2. Athenaのテーブルを作成して、クエリを投げる
    • ただテーブル作成して投げる場合
    • 入れ子になっているjsonに対してテーブル作成してクエリを投げる場合
    • 禁止文字を含んでいるものに対してテーブルを作成してクエリを投げる場合
      わかりづらいですが、禁止文字を含む項目をマッピングする項目とクエリを投げる項目が分かれています。)
  3. hive-json-schemaの紹介
    jsonからテーブル作成のためのクエリを生成するツールっぽいのですが、紹介してるわりにちっとも使い方が書いてません…。
    使い方の解説をどなたか…。

ハマったところ

入れ子になったjsonに対するテーブル作成について

ハマったところといいつつ、自分はこの辺は割とスムーズに行ったのですが、ちょっとわかりづらいかもしれないので念のため。
サンプルにもありますが、jsonの中にまたjsonとか配列とかが入っている、みたいなケースは多くあります。
そういった場合、内部にあるjsonに対してstruct型を使って、その下の項目について型を定義してやればOKです。
その中にさらにjsonがある場合はさらにその中にstruct型で定義をすればOK。
例にあるものだと、内部にmail{~}とjsonがあり、その中にさらにいくつかのjsonがあるので、それぞれに対してstruct型で定義をしています。
以下引用(全文は貼っていないので、元はリンク先を見てください。)
※一部バッククオート(`)で囲われている項目がありますが、予約語として使われている言葉をそのまま使用するとエラーになるそうです。
そのため、バッククオートで囲うことによってエスケープしてるようです。

抜粋した入れ子の部分
 mail struct<`timestamp`:string,
              source:string,
              sourceArn:string,
              sendingAccountId:string,
              messageId:string,
              destination:string,
              headersTruncated:boolean,
              headers:array<struct<name:string,value:string>>,
              commonHeaders:struct<`from`:array<string>,to:array<string>,messageId:string,subject:string>
              > 

禁止文字そのものについて

まず、禁止文字が色々あることを最初大して理解してませんでした。
項目名(↑の例だと、timestampとかsourceとかのところ)の定義に使用できない文字があります。
記事中だと「:」(コロン)が禁止文字列なので、それがクエリ中の該当箇所に入っているとエラーになります。
あとは「-」(ハイフン)なんかも禁止文字のようです。
例えばHTTPリクエストのログを見たとき、ヘッダとかはハイフンを使った項目がいくつもあったりするので困りますよね。
一応記事中の例では両方「_」(アンダースコア)に変換しています。(コロンにしか触れてませんが…。)
最初は禁止文字があると知らず、なぜエラーになっているかわからずにハマってました。
この辺どっかにまとまってるのかな…?
どうやって回避するかというと、それがWITH SERDEPROPERTIESの部分です。

禁止文字を含む場合のマッピングの仕方について

最初見た時はなんでこんなことをするのかわかりませんでしたが、上記の通り項目名を定義するときに禁止文字が入っているとエラーになります。
なので、WITH SERDEPROPERTIESの項目で、禁止文字列を含んだ項目名を、禁止文字列のない文字列にマッピングし、元のjsonのkeyでは禁止文字列を含んでいたものに対し、テーブル上ではカラム名として別の文字列をあてがうことができます。
記事中では、コロンやハイフンをアンダースコアに変換した文字列にマッピングしています。
式の左側がカラム名に使いたい文字列で、それに対して右側がデータの元の実際の名前です。
"mapping.カラム名に使いたい文字列"="実際の名前" みたいに記述してます。

マッピングの仕方

WITH SERDEPROPERTIES (
  "mapping.ses_configurationset"="ses:configuration-set",
  "mapping.ses_source_ip"="ses:source-ip", 
  "mapping.ses_from_domain"="ses:from-domain", 
  "mapping.ses_caller_identity"="ses:caller-identity"
  )

クエリの投げ方

これもまあおまけで書いておくと、ここまでしっかりと下の項目までテーブルを定義しておくと、下の項目までクエリで引っ張ることが出来ます。
記事中では下記のような例が出ています。

元記事にある例
SELECT eventtype as Event,
         mail.timestamp as Timestamp,
         mail.tags.ses_source_ip as SourceIP,
         mail.tags.ses_caller_identity as AuthenticatedBy,
         mail.commonHeaders."from" as FromAddress,
         mail.commonHeaders.to as ToAddress
FROM sesblog2
WHERE eventtype = 'Bounce'

mail{〜}の下の項目を参照する時は上記のようにドットをつけて該当項目の名前を指定しています。
さらにその下の項目を参照する時はその後ろにさらにドットをつけています。
この辺は直感的にわかりやすいかもしれません。

おまけとかtipsとか

Firehoseで配置されたフォルダ構成ではパーティションを自動で切ってもらえない

hiveではフォルダが/bucketname/path/to/log/year=YY/month=MM/day=dd/foo
みたいな構成だと自動でパーティション設定してくれるらしいのですが、FirehoseでS3にデータ配置すると/bucketname/path/to/log/YYYY/MM/DD/fooみたいになるので、自分でパーティションを作成する必要があります。
パーティションがない状態でクエリを投げても1件も引っかかりません。
これを作るには下記のようなクエリを投げる必要があります。

elbログを対象としたテーブルにパーティションを作成する場合
ALTER TABLE database_name.table_name
ADD PARTITION (year='2016',month='08',day='28')
location 's3://elb-access-log/AWSLogs/00000000000000/elasticloadbalancing/ap-northeast-1/2016/08/28/';

※参考
https://qiita.com/r4-keisuke/items/d3d339b76d4368b6b30a

上記の例だと1日ずつパーティションを設定する必要があるのですが、
パーティション数には上限があるらしい(1テーブル20000まで)ので、1日ずつとか、1時間ずつとかフォルダ分けしている場合はちょっと注意が必要かもしれないです。
※パーティションの上限については下記
https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws_service_limits.html#limits_glue
さすがに対象が多すぎとなるとしんどいので、シェルスクリプトとかで回すといいと思います。
ただ、シェルスクリプト自体も1つ1つの処理実行だとそこそこ時間かかるのと、パーティションを設定するためのクエリでクエリ履歴が埋め尽くされるのが難点です。

データ元にない項目を定義しても値がnullになるだけで問題はない

jsonの出力が一定じゃなくて、いくつかの似たような型のjsonが混ざっていたり、ものによって存在しない項目があったりしても、それらのキーを全て網羅するようにまとめて定義しちゃって問題ないみたいです。
定義したけどデータ元に項目がない場合はnullが入るだけのようで。
逆に元データにある項目を全部定義する必要はないので、元データにあっても使わないような項目はテーブル作成の段階で定義しないようにしてもいいみたいですね。

ざっと書いたので、わかりづらいとか、もっとこうすればみたいな指摘があればいただけると嬉しいです。

続きを読む

近況 – eigokun

今はクラウド市場はAWSと他のサービスがシェアを分け合ってるけど、コンピュータアーキテクチャの常として、必ずその上のレイヤーに独占的なサービスを築く企業が出てくる気がする。 by eigokun 2017/12/27 17:30:19 from mail 返信. 書き込むには、ログインまたはユーザー登録を行ってください。 初めての方へ · 規約違反 … 続きを読む

カテゴリー 未分類 | タグ

AWS Cloud9 で Ruby on Rails を試してみた

2017/11/30にAWSにて、クラウド型統合IDE Cloud9がローンチされましたので。
さっそく、Railsアプリケーションで試してみました。
AWS Cloud9 – クラウド開発環境

今回使用した環境

クライアントPC:mac book pro
ブラウザ:chrome
AWS使用サービス:
 CodeStar、EC2(t2.micro)、Cloud9

前提

以下の手順は、IAMユーザで行っています。
AWSは、ルートアカウントとは別に管理アカウントを複数作成できます。
これらのアカウントをAWSでは、IAMユーザと呼んでいる様ですが、
こうして作成した、IAMユーザは、クレジット情報などへの
アクセスをさせずに、管理業務だけを委任したりできるため、大変便利です。
AWS アカウント内での IAM ユーザーの作成

CodeStarによる環境セットアップ

まず、EC2にnginx+railsの環境を作成するため、CodeStarを使用します。
EC2のインスタンスを起動して一から必要なものをインストールしても良いのですが、
CodeStar使用すると、その辺の事をよしなにやってくれます。

まず、キーペアがひとつ必要です。
無い場合は、下記の手順で作成しておきます。
Amazon EC2 のキーペア

作成し終えたら、CodeStarのコンソールにアクセスします。
AWSにログインした状態で、以下のアドレスです。
CodeStarコンソール

スクリーンショット 2017-12-19 22.34.37.png

「Start a project」をクリックして、テンプレート選択画面に遷移しましょう。

スクリーンショット 2017-12-19 22.37.31.png

作成可能なテンプレートがたくさん並んでいます。
Railsに関しては、AWS Elastic Beanstalk版とEC2版がありますが、
今回は、簡易的な動作確認ですので、EC2版を選択します。
おそらく、Sqlite3を使った1インスタンスの最小構成で作成されるはずです。
mysqlなどのRDBを使ったり、ロードバランサを置いたりしたい場合は、
AWS Elastic Beanstalk版を選択すると良いと思います。

次はプロジェクト名とリポジトリ名を入力します。

スクリーンショット 2017-12-19 22.44.17.png

両方ともに「RailsSample」にしました。
リポジトリ管理には、AWS CodeCommit か GitHub のどちらかを選択できます。
どちらを選んでも、Gitでのバージョン管理になります。
CodeCommitは、GitHubの簡易版の様な位置づけでしたが、
2017年11月には、pullリクエストの作成もサポートされたらしく
徐々に使える様になってきている感じでしょうか。

今回は、CodeCommitを選択しました。

Create Project を選択すると、キー選択画面がポップアップされますので、
はじめに作成したキーペアを選択します。

スクリーンショット 2017-12-19 22.55.12.png

使用するIDEを選択する画面になりました。
ここで、本題のAWS Cloud9が出てきます。

スクリーンショット 2017-12-19 22.57.18.png

ここは当然、Cloud9を選択してみました。

スクリーンショット 2017-12-19 23.00.43.png

Cloud9を動作させるためのインスタンスの種類を選択します。
無料枠で試すには、t2.micro を選択します。

スクリーンショット 2017-12-19 23.03.24.png

環境の準備中です。
至るところクルクルしてますので、
しばし、待ちます。(5分くらいかな…)

スクリーンショット 2017-12-19 23.10.37.png

デプロイステータスが上記の様になり、
ヘッダ部が以下の様に変わったら、準備OKだと思います。

スクリーンショット 2017-12-19 23.12.29.png

View your app でRailsのサンプル・アプリケーションにアクセスできます。

スクリーンショット 2017-12-19 23.14.42.png

しゃれおつ。

Start coding の方を選択すると、Cloud9の画面に遷移します。

AWS Cloud9

スクリーンショット 2017-12-19 23.23.59.png

IDEっぽいです。
ブラウザだけでこれだけできるなんて、時代が進んだのを感じます。

ディレクトリツリーを見ると、Railsっぽい環境になっているのがわかります。
下部のペインは、EC2のコンソールになっていて、
シェルコマンドはここから実行できます。

RailsUser01:~/environment/railssample (master) $ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]
RailsUser01:~/environment/railssample (master) $ gem list

*** LOCAL GEMS ***

actioncable (5.1.4)
actionmailer (5.1.4)
actionpack (5.1.4)
actionview (5.1.4)
activejob (5.1.4)
activemodel (5.1.4)
activerecord (5.1.4)
activesupport (5.1.4)
arel (8.0.0)
bigdecimal (default: 1.3.0)
builder (3.2.3)
bundler (1.15.4)
bundler-unload (1.0.2)
concurrent-ruby (1.0.5)
crass (1.0.2)
did_you_mean (1.1.0)
erubi (1.7.0)
executable-hooks (1.3.2)
gem-wrappers (1.3.2)
globalid (0.4.1)
i18n (0.9.0)
io-console (default: 0.4.6)
json (default: 2.0.2)
loofah (2.1.1)
mail (2.6.6)
method_source (0.9.0)
mime-types (3.1)
mime-types-data (3.2016.0521)
mini_portile2 (2.3.0)
minitest (5.10.1)
net-telnet (0.1.1)
nio4r (2.1.0)
nokogiri (1.8.1)
openssl (default: 2.0.3)
power_assert (0.4.1)
psych (default: 2.2.2)
rack (2.0.3)
rack-test (0.7.0)
rails (5.1.4)
rails-dom-testing (2.0.3)
rails-html-sanitizer (1.0.3)
railties (5.1.4)
rake (12.0.0)
rdoc (default: 5.0.0)
rubygems-bundler (1.4.4)
rvm (1.11.3.9)
sprockets (3.7.1)
sprockets-rails (3.2.1)
test-unit (3.2.3)
thor (0.20.0)
thread_safe (0.3.6)
tzinfo (1.2.3)
websocket-driver (0.6.5)
websocket-extensions (0.1.2)
xmlrpc (0.2.1)

現環境では、rubyは2.4.1、railsは、5.1.4の様です。
何か機能を足してみましょう。

RailsUser01:~/environment $ cd ./railssample
RailsUser01:~/environment/railssample (master) $ rails g scaffold blog title:string body:text
Usage:
  rails new APP_PATH [options]

Options:
  -r, [--ruby=PATH]                                      # Path to the Ruby binary of your choice
                                                         # Default: /usr/local/rvm/rubies/ruby-2.4.1/bin/ruby
  -m, [--template=TEMPLATE]                              # Path to some application template (can be a filesystem path or URL)
  -d, [--database=DATABASE]                              # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/frontbase/ibm_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)

...以下略

おや?
エラーです。。。Usageが出ますね。。。

お気付きでしょうか?
ディレクトリツリーを見てわかる様にbinフォルダが存在しませんね。。。
なぜかは、わかりませんが、
rakeで作成できるはずなので、やってみます。

RailsUser01:~/environment/railssample (master) $ rake app:update:bin
Could not find gem 'passenger' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

ん?
gem ‘passenger’ がいない?
確かに前述の gem list とGemfileの内容が一致していない様に思います。

bundle isntall します。

RailsUser01:~/environment/railssample (master) $ bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/.........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...
Fetching rake 11.2.2
Installing rake 11.2.2
Fetching concurrent-ruby 1.0.2
Installing concurrent-ruby 1.0.2
Fetching i18n 0.7.0
Installing i18n 0.7.0
Fetching minitest 5.9.0
Installing minitest 5.9.0
Fetching thread_safe 0.3.5
Installing thread_safe 0.3.5
Fetching builder 3.2.2
Installing builder 3.2.2
Fetching erubis 2.7.0
Installing erubis 2.7.0
Fetching mini_portile2 2.1.0
Installing mini_portile2 2.1.0
Fetching pkg-config 1.1.7
Installing pkg-config 1.1.7
Fetching rack 2.0.1
Installing rack 2.0.1
Fetching nio4r 1.2.1
Installing nio4r 1.2.1 with native extensions
Using websocket-extensions 0.1.2
Using mime-types-data 3.2016.0521
Fetching arel 7.1.1
Installing arel 7.1.1
Using bundler 1.15.4
Fetching byebug 9.0.5
Installing byebug 9.0.5 with native extensions
Fetching coffee-script-source 1.10.0
Installing coffee-script-source 1.10.0
Fetching execjs 2.7.0
Installing execjs 2.7.0
Fetching method_source 0.8.2
Installing method_source 0.8.2
Fetching thor 0.19.1
Installing thor 0.19.1
Fetching debug_inspector 0.0.2
Installing debug_inspector 0.0.2 with native extensions
Fetching ffi 1.9.14
Installing ffi 1.9.14 with native extensions
Fetching multi_json 1.12.1
Installing multi_json 1.12.1
Fetching libv8 3.16.14.19 (x86_64-linux)
Installing libv8 3.16.14.19 (x86_64-linux)
Fetching rb-fsevent 0.9.7
Installing rb-fsevent 0.9.7
Fetching puma 3.0.0
Installing puma 3.0.0 with native extensions
Fetching ref 2.0.0
Installing ref 2.0.0
Fetching sass 3.4.22
Installing sass 3.4.22
Fetching tilt 2.0.5
Installing tilt 2.0.5
Fetching spring 1.7.2
Installing spring 1.7.2
Fetching sqlite3 1.3.11
Installing sqlite3 1.3.11 with native extensions
Fetching turbolinks-source 5.0.0
Installing turbolinks-source 5.0.0
Fetching tzinfo 1.2.2
Installing tzinfo 1.2.2
Fetching nokogiri 1.6.8
Installing nokogiri 1.6.8 with native extensions
Fetching rack-test 0.6.3
Installing rack-test 0.6.3
Fetching passenger 5.1.12
Installing passenger 5.1.12 with native extensions
Fetching sprockets 3.7.0
Installing sprockets 3.7.0
Fetching websocket-driver 0.6.4
Installing websocket-driver 0.6.4 with native extensions
Using mime-types 3.1
Fetching coffee-script 2.4.1
Installing coffee-script 2.4.1
Fetching uglifier 3.0.1
Installing uglifier 3.0.1
Fetching rb-inotify 0.9.7
Installing rb-inotify 0.9.7
Fetching therubyracer 0.12.3
Installing therubyracer 0.12.3 with native extensions
Fetching turbolinks 5.0.0
Installing turbolinks 5.0.0
Fetching activesupport 5.0.0
Installing activesupport 5.0.0
Fetching loofah 2.0.3
Installing loofah 2.0.3
Fetching mail 2.6.4
Installing mail 2.6.4
Fetching listen 3.0.5
Installing listen 3.0.5
Fetching rails-dom-testing 2.0.1
Installing rails-dom-testing 2.0.1
Fetching globalid 0.3.7
Installing globalid 0.3.7
Fetching activemodel 5.0.0
Installing activemodel 5.0.0
Fetching jbuilder 2.5.0
Installing jbuilder 2.5.0
Using rails-html-sanitizer 1.0.3
Fetching spring-watcher-listen 2.0.0
Installing spring-watcher-listen 2.0.0
Fetching activejob 5.0.0
Installing activejob 5.0.0
Fetching activerecord 5.0.0
Installing activerecord 5.0.0
Fetching actionview 5.0.0
Installing actionview 5.0.0
Fetching actionpack 5.0.0
Installing actionpack 5.0.0
Fetching actioncable 5.0.0
Installing actioncable 5.0.0
Fetching actionmailer 5.0.0
Installing actionmailer 5.0.0
Fetching railties 5.0.0
Installing railties 5.0.0
Fetching sprockets-rails 3.1.1
Installing sprockets-rails 3.1.1
Fetching coffee-rails 4.2.1
Installing coffee-rails 4.2.1
Fetching jquery-rails 4.1.1
Installing jquery-rails 4.1.1
Fetching web-console 3.3.1
Installing web-console 3.3.1
Fetching rails 5.0.0
Installing rails 5.0.0
Fetching sass-rails 5.0.6
Installing sass-rails 5.0.6
Bundle complete! 17 Gemfile dependencies, 67 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

入ったっぽい。
再度、rakeにトライ。
今度は。bundle execつき

RailsUser01:~/environment/railssample (master) $ bundle exec rake app:update:bin                                                                               
/usr/local/rvm/gems/ruby-2.4.1/gems/rake-11.2.2/lib/rake/ext/fixnum.rb:4: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:206: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/xml_mini.rb:51: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/xml_mini.rb:52: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/digest_utils.rb:47: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/digest_utils.rb:51: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/processor_utils.rb:110: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/processor_utils.rb:111: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  bin/update
RailsUser01:~/environment/railssample (master) $ 

できた。
再度、scaffold。

RailsUser01:~/environment/railssample (master) $ rails g scaffold blog title:string body:text
/usr/local/rvm/gems/ruby-2.4.1/gems/rake-11.2.2/lib/rake/ext/fixnum.rb:4: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:206: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/xml_mini.rb:51: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/xml_mini.rb:52: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/digest_utils.rb:47: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/digest_utils.rb:51: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/processor_utils.rb:110: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/processor_utils.rb:111: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/core_ext/numeric/conversions.rb:138: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
      invoke  active_record
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
      create    db/migrate/20171219144809_create_blogs.rb
      create    app/models/blog.rb
      invoke    test_unit
      create      test/models/blog_test.rb
      create      test/fixtures/blogs.yml
      invoke  resource_route
       route    resources :blogs
      invoke  scaffold_controller
      create    app/controllers/blogs_controller.rb
      invoke    erb
      create      app/views/blogs
      create      app/views/blogs/index.html.erb
      create      app/views/blogs/edit.html.erb
      create      app/views/blogs/show.html.erb
      create      app/views/blogs/new.html.erb
      create      app/views/blogs/_form.html.erb
      invoke    test_unit
      create      test/controllers/blogs_controller_test.rb
      invoke    helper
      create      app/helpers/blogs_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/blogs/index.json.jbuilder
      create      app/views/blogs/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/blogs.coffee
      invoke    scss
      create      app/assets/stylesheets/blogs.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss
RailsUser01:~/environment/railssample (master) $ 

migrateも成功。

RailsUser01:~/environment/railssample (master) $ bundle exec rails db:migrate
/usr/local/rvm/gems/ruby-2.4.1/gems/rake-11.2.2/lib/rake/ext/fixnum.rb:4: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:206: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/xml_mini.rb:51: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/xml_mini.rb:52: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/digest_utils.rb:47: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/digest_utils.rb:51: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/processor_utils.rb:110: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/sprockets-3.7.0/lib/sprockets/processor_utils.rb:111: warning: constant ::Bignum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/activesupport-5.0.0/lib/active_support/core_ext/numeric/conversions.rb:138: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
== 20171219144809 CreateBlogs: migrating ======================================
-- create_table(:blogs)
   -> 0.0009s
== 20171219144809 CreateBlogs: migrated (0.0017s) =============================

/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated
/usr/local/rvm/gems/ruby-2.4.1/gems/concurrent-ruby-1.0.2/lib/concurrent/map.rb:230: warning: constant ::Fixnum is deprecated

このまま、コミットして、masterにpushします。

RailsUser01:~/environment/railssample (master) $ git add .
RailsUser01:~/environment/railssample (master) $ git commit -m 'blogs追加'
[master c39da40] blogs追加
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global user.name "Your Name"
    git config --global user.email you@example.com

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 28 files changed, 454 insertions(+), 1 deletion(-)
 create mode 100644 app/assets/javascripts/blogs.coffee
 create mode 100644 app/assets/stylesheets/blogs.scss
 create mode 100644 app/assets/stylesheets/scaffolds.scss
 create mode 100644 app/controllers/blogs_controller.rb
 create mode 100644 app/helpers/blogs_helper.rb
 create mode 100644 app/models/blog.rb
 create mode 100644 app/views/blogs/_form.html.erb
 create mode 100644 app/views/blogs/edit.html.erb
 create mode 100644 app/views/blogs/index.html.erb
 create mode 100644 app/views/blogs/index.json.jbuilder
 create mode 100644 app/views/blogs/new.html.erb
 create mode 100644 app/views/blogs/show.html.erb
 create mode 100644 app/views/blogs/show.json.jbuilder
 create mode 100755 bin/bundle
 create mode 100755 bin/rails
 create mode 100755 bin/rake
 create mode 100755 bin/setup
 create mode 100755 bin/update
 create mode 100644 db/migrate/20171219144809_create_blogs.rb
 create mode 100644 db/schema.rb
 create mode 100644 test/controllers/blogs_controller_test.rb
 create mode 100644 test/fixtures/blogs.yml
 create mode 100644 test/models/blog_test.rb
 create mode 100644 tmp/restart.txt
RailsUser01:~/environment/railssample (master) $ git config --global user.name "RailsUser01"
RailsUser01:~/environment/railssample (master) $ git config --global user.email test@test.com
RailsUser01:~/environment/railssample (master) $ git push
Counting objects: 47, done.
Compressing objects: 100% (44/44), done.
Writing objects: 100% (47/47), 8.65 KiB | 632.00 KiB/s, done.
Total 47 (delta 7), reused 0 (delta 0)
To https://xxxxx/v1/repos/RailsSample
   01dc390..c39da40  master -> master

すると、CodeStarの方で、pushを検知して、再びデプロイが始まります。
デプロイが終わったら、/blogsでアクセス。

スクリーンショット 2017-12-20 0.01.55.png

できたっぽい。
layoutが、しゃれおつのままなので、逆に見た目がアレですが、
一応、これで、コーディングして行けるっぽいです。

この先のこと

この環境では、EC2(Cloud9開発環境) -> EC2(動作確認環境)にいちいちデプロイして
確認することになるので、ちょっと面倒。
実用的に使うには、EC2(Cloud9開発環境) だけで、動作確認できる様にして、
ステージングやプロダクション環境には、必要に応じてデプロイ出来る様にしたいですね。

EC2(Cloud9開発環境)側にenginxを入れるか、
何かしらの制限付きでpumaのポートを開放するかになるのかな?
まだまだ、いろいろ試せそうな感じですが、
Cloud9ともども、おいおい試していきたいです。

試すだけなら無料枠で出来るので、皆様もいつかの機会にどうでしょうか?
今回はここまでです。

続きを読む

スマートホームスキルの作成 – Cognito User Poolsを使って

スマートホームスキルとは

スマートホームスキルは、カスタムスキルと異なり、ユーザーが発話した内容を、プログラム用意なパラメーターにまで分解して呼び出してくれます。例えば、照明をつける場合、「Alexa、端末名をオンにして」といういうと、以下のようなDirectivesという形でスマートホームスキルが呼び出されます。

{
  "directive": {
    "header": {
      "namespace": "Alexa.PowerController",
      "name": "TurnOn",
      "payloadVersion": "3",
      "messageId": "1bd5d003-31b9-476f-ad03-71d471922820",
      "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg=="
    },
    "endpoint": {
      ...
}

一般的なWebAPIの呼び出しと変わらない形で呼んでくれますので、会話の組み立てなどを考える必要がなく開発する側は非常に楽です。

image.png

カスタムスキルの場合は、会話が分解されて文字列としてスキル側に渡されます。そののため各言語ごとの処理が必要になります。しかしスマートホームスキルの場合は、その辺りの処理はAlexaが行ってくれるので、他言語化対応ができます。

以下の照明のON/OFFの場合、英語(米国)、英語(英国)、英語(インド)、ドイツ語、日本語の5ヶ国語に対応が可能です。
https://developer.amazon.com/ja/docs/device-apis/alexa-powercontroller.html

やりたいこと

自前デバイスを制御するためのスマートホームスキルの作成方法を理解する。デバイスは自前クラウド経由で制御が可能。自前クラウドはCognito User Poolsでアカウント管理を行っており、AlexaのAmazonアカウントと連携して制御可能とする。(アカウントリンク)

開発者コンソールでスキルの登録

Amazon開発者コンソールでスキルを登録します。
https://developer.amazon.com/home.html

スクリーンショット 2017-12-17 12.30.01.png

以下のように設定してスキルを作成します。後からLambdaなどの設定をするので、まずははアプリケーションIDだけ取れれば良いです。
スクリーンショット 2017-12-17 12.30.41.png

AWS Lambdaでスマートホームスキルの作成

AWSコンソールでスマートホームスキルをLambdaで作成します。Lambdaには、スマートホームスキルの雛形が用意されていますので、まずはそれを利用します。”設計図”、”alexa-smart-home-skill-adapter”を選択します。Amazonに確認したわけではないですが、tokyoリージョンにLambdaを作成した時は後にエラーになりました。us-east-1を選択して下さい。
スクリーンショット 2017-12-17 16.42.19.png

以下のようにLambdaの設定をします。アプリケーションIDは先ほど作成したスマートホームスキルのIDを設定します。
スクリーンショット 2017-12-17 16.57.45.pngスクリーンショット 2017-12-17 16.57.53.png

Lambdaが作成されました。後ほどARNをスキルの設定で利用します。
スクリーンショット 2017-12-17 17.03.40.png

Cognito User Poolsの設定

次にアカウント管理のためのCognito User Poolsの設定をしていきます。AWSコンソールよりユーザープールを作成します。ステップに従って設定するを選択します。
スクリーンショット 2017-12-17 17.07.23.png

スクリーンショット 2017-12-17 17.08.28.png

基本的な仕組みを確認するためなので、属性などはここでは必要ありません。以下のように設定をしていきます。

  • 属性

    • 標準属性 – なし
    • カスタム属性 – なし
  • ポリシー
    • パスワード強度:デフォルト 8文字
    • ユーザーの自己サインアップ:デフォルト 許可する
    • 有効期限:デフォルト 7日間
  • MFA
    • MFA:OFF
    • 検証:e-mailのみ
  • メッセージのカスタマイズ
    • E メール検証メッセージをカスタマイズ:デフォルト
    • ユーザー招待メッセージをカスタマイズ:デフォルト
    • E メールアドレスをカスタマイズ:デフォルト
  • タグ/デバイスはスキップして次へを選択
  • 「アプリクライアント」で「アプリクライアントの追加」

スクリーンショット 2017-12-17 17.34.02.png

あとは次へを選択してプールを作成。次にスマートホームスキルに設定するための以下の二つの情報を取得。

  • アプリクライアントID
  • 「アプリの統合」「ドメイン名」をクリックして、適当なドメインを設定
    スクリーンショット 2017-12-17 17.45.42.png

スクリーンショット 2017-12-17 21.45.48.png

スキルの設定

Alexaのコンソールの設定に戻って、以下の設定をします。

  • LambdaのARNの設定
  • アカウントリンクの設定

スクリーンショット 2017-12-17 21.47.49.png

スクリーンショット 2017-12-17 21.48.01.png

コールバックURIは次の設定で利用します。

スクリーンショット 2017-12-17 21.49.00.png

Cognito User PoolsにコールバックURIの設定

先ほど取得したOAuthのコールバックURIを以下のように設定します。

スクリーンショット 2017-12-17 22.00.35.png

動作確認

後ほどアップデートします

続きを読む

Solve a AWS IoT script problem

さらに表示: aws linux script, aws bash script, youtube clone script problem upload, aws download script, php script price details products ebay amazon, linux php send mail script problem, instal script login details website dreamweaver, connect aws java script, shell script file details, telebid script … 続きを読む

AWS 上に JobScheduler を構築した話

AWSには自前でジョブ実行用のサービスがあるので、こういうのはあまりやらないと思いますが、色々とハマったのでメモ。

インストール

JobScheduler インストール

  1. インストーラーをここからダウンロードします。

    スクリーンショット 2017-06-08 0.33.37.png

  2. 解凍した中にある jobscheduler_install.xml を以下の形に修正します。

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <!--
    XML configuration file for JobScheduler setup
    
    The JobScheduler is available with a dual licensing model.
    - GNU GPL 2.0 License (see http://www.gnu.org/licenses/gpl-2.0.html)
    - JobScheduler Commercial License (see licence.txt)
    
    The setup asks you for the desired license model
    (see <entry key="licenceOptions" .../> below).
    
    If you call the setup with this XML file then you accept
    at the same time the terms of the chosen license agreement.
    -->
    <AutomatedInstallation langpack="eng">
    <com.izforge.izpack.panels.UserInputPanel id="home">
        <userInput/>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="licences">
        <userInput>
    
            <!-- Select the license model (GPL or Commercial) -->
            <entry key="licenceOptions" value="GPL"/>
    
            <!-- If you selected GPL as license model than the licence must be empty.
                 Otherwise please enter a license key if available.
                 It is also possible to modify the license key later. -->
            <entry key="licence" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.HTMLLicencePanel id="gpl_licence"/>
    <com.izforge.izpack.panels.HTMLLicencePanel id="commercial_licence"/>
    <com.izforge.izpack.panels.TargetPanel id="target">
    
        <!-- SELECT THE INSTALLATION PATH FOR THE BINARIES AND LIBRARIES
             The installation expands this path with the Scheduler ID as subdirectory.
             The path must be absolute!
             Default paths are
             /opt/sos-berlin.com/jobscheduler for Unix
             C:Program Filessos-berlin.comjobscheduler for Windows -->
        <installpath>/opt/sos-berlin.com/jobscheduler</installpath>
    
    </com.izforge.izpack.panels.TargetPanel>
    <com.izforge.izpack.panels.UserPathPanel id="userpath">
    
        <!-- SELECT THE DATA PATH FOR CONFIGURATION AND LOG FILES
             The installation expands this path with the Scheduler ID as subdirectory.
             The path must be absolute!
             Default paths are
             /home/[user]/sos-berlin.com/jobscheduler for Unix
             C:ProgramDatasos-berlin.comjobscheduler for Windows -->
        <UserPathPanelElement>/home/joc/jobscheduler</UserPathPanelElement>
    
    </com.izforge.izpack.panels.UserPathPanel>
    <com.izforge.izpack.panels.PacksPanel id="package">
    
        <!-- SELECT THE PACKS WHICH YOU WANT INSTALL -->
    
        <!-- Package: JobScheduler
             JobScheduler Basic Installation
             THIS PACK IS REQUIRED. IT MUST BE TRUE -->
        <pack index="0" name="Job Scheduler" selected="true"/>
    
        <!-- Package: Database Support
             Job history and log files can be stored in a database. Database support is
             available for MySQL, PostgreSQL, Oracle, SQL Server, DB2.
             THIS PACK IS REQUIRED. IT MUST BE TRUE -->
        <pack index="2" name="Database Support" selected="true"/>
    
        <!-- Package: Housekeeping Jobs
             Housekeeping Jobs are automatically launched by the Job Scheduler, e.g. to send
             buffered logs by mail, to remove temporary files or to restart the JobScheduler. -->
        <pack index="5" name="Housekeeping Jobs" selected="true"/>
    
    </com.izforge.izpack.panels.PacksPanel>
    <com.izforge.izpack.panels.UserInputPanel id="network">
        <userInput>
            <!-- Network Configuration -->
    
            <!-- Enter the name or ip address of the host on which the JobScheduler is operated -->
            <entry key="schedulerHost" value="localhost"/>
    
            <!-- Enter the port for TCP communication -->
            <entry key="schedulerPort" value="4444"/>
    
            <!-- Enter the port for HTTP communication -->
            <entry key="schedulerHTTPPort" value="40444"/>
    
            <!-- To enter a JobScheduler ID is required.
                 The IDs of multiple instances of the JobScheduler must be unique per server.
                 The JobScheduler ID expands the above installation paths as subdirectory.
                 Please omit special characters like: /  : ; * ? ! $ % & " < > ( ) | ^ -->
            <entry key="schedulerId" value="schedulerId_XXXX"/>
    
            <!-- It is recommended to enable TCP access for the host where the JobScheduler will install,
                 optionally enter additional host names or ip addresses. To enable all hosts in your
                 network to access the JobScheduler enter '0.0.0.0'. -->
            <entry key="schedulerAllowedHost" value="0.0.0.0"/>
    
            <!-- Choose (yes or no) wether the JobScheduler should be started at the end of the installation -->
            <entry key="launchScheduler" value="yes"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="cluster">
        <userInput>
            <!-- Cluster Configuration -->
    
            <!-- The JobScheduler can be installed independent of other possibly JobSchedulers,
                 as a primary JobScheduler in a backup system or as a backup JobScheduler.
                 Use '' for a standalone, '-exclusive' for a primary
                 or '-exclusive -backup' for a backup JobScheduler.
                 A database is required for a backup system. All JobSchedulers in a backup system
                 must have the same JobScheduler ID and the same database.
                 Further you can set '-distributed-orders' for a load balancing cluster.
                 For more information see
                 http://www.sos-berlin.com/doc/de/scheduler.doc/backupscheduler.xml
                 http://www.sos-berlin.com/doc/de/scheduler.doc/distributed_orders.xml -->
            <entry key="clusterOptions" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="smtp">
        <userInput>
            <!-- Mail Recipients Configuration / SMTP Authentication -->
    
            <!-- Enter the ip address or host name and port (default: 25) of your SMTP server -->
            <entry key="mailServer" value=""/>
            <entry key="mailPort" value="25"/>
    
            <!-- Configure the SMTP authentication if necessary. -->
            <entry key="smtpAccount" value=""/>
            <entry key="smtpPass" value=""/>
    
            <!-- Enter the addresses of recipients to which mails with log files are automatically
                 forwarded. Separate multiple recipients by commas -->
    
            <!-- Account from which mails are sent -->
            <entry key="mailFrom" value=""/>
    
            <!-- Recipients of mails -->
            <entry key="mailTo" value=""/>
    
            <!-- Recipients of carbon copies: -->
            <entry key="mailCc" value=""/>
    
            <!-- Recipients of blind carbon copies -->
            <entry key="mailBcc" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="email">
        <userInput>
            <!-- Mail Configuration / Event Handler -->
    
            <!-- Choose in which cases mails with log files are automatically forwarded. -->
            <entry key="mailOnError" value="no"/>
            <entry key="mailOnWarning" value="no"/>
            <entry key="mailOnSuccess" value="no"/>
    
            <!-- The Housekeeping package is required for configure JobScheduler as event handler
                 Choose this option if you intend to use JobScheduler Events and
                 - this JobScheduler instance is the only instance which processes Events
                 - this JobScheduler instance is a supervisor for other JobSchedulers which submit Events -->
            <entry key="jobEvents" value="off"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="database">
        <userInput>
            <!-- JobScheduler Database Configuration -->
    
            <!-- Choose the database management system. Supported values are 'mysql' for MySQL,
                 'oracle' for Oracle, 'mssql' for MS SQL Server, 'pgsql' for PostgreSQL,
                 'db2' for DB2 and 'sybase' for Sybase. -->
            <entry key="databaseDbms" value="mysql"/>
    
            <!-- You can choose between 'on' or 'off' to create the database tables.
                 If you have modified the initial data of an already existing installation,
                 then the modifications will be undone. Data added remains unchanged.
                 This entry should be only 'off', when you sure, that all tables are already created. -->
            <entry key="databaseCreate" value="on"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="dbconnection">
        <userInput>
            <!-- JobScheduler Database Configuration -->
    
            <!-- Enter the name or ip address of the database host -->
            <entry key="databaseHost" value="the host of RDS"/>
    
            <!-- Enter the port number for the database instance. Default ports are for MySQL 3306,
                 Oracle 1521, MS SQL Server 1433, postgreSQL 5432, DB2 50000, Sybase 5000. -->
            <entry key="databasePort" value="3306"/>
    
            <!-- Enter the schema -->
            <entry key="databaseSchema" value="jobscheduler_data"/>
    
            <!-- Enter the user name for database access -->
            <entry key="databaseUser" value="databaseUser"/>
    
            <!-- Enter the password for database access -->
            <entry key="databasePassword" value="databasePassword"/>
    
            <!-- You have to provide the MySQL, MS SQL Server, Sybase or DB2 JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL, Sybase and MS SQL Server JDBC Drivers are
                 not provided. Alternatively you can use the mariadb JDBC Driver for MySQL and
                 the jTDS JDBC Driver for MS SQL Server and Sybase which is provided. -->
    
            <!-- You can choose between 'yes' or 'no' for using the jTDS JDBC Driver
                 This entry affects only MS SQL Server or Sybase -->
            <entry key="connectorJTDS" value="yes"/>
    
            <!-- You can choose between 'yes' or 'no' for using the mariadb JDBC Driver
                 This entry affects only MySQL -->
            <entry key="connectorMaria" value="yes"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="jdbc">
        <userInput>
            <!-- JobScheduler Database Configuration -->
    
            <!-- Configuration for JDBC Driver
                 This entry is only necessary if you selected a DBMS type such as MySQL,
                 MS SQL Server, Sybase ot DB2 in the previous <userInput> element. -->
    
            <!-- You have to provide the MySQL, MS SQL Server, Sybase or DB2 JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL and MS SQL Server JDBC Drivers are
                 not provided. Specify the JDBC Driver source (e.g. mysql-connector-java-*.jar for MySQL,
                 sqljdbc.jar for MS SQL Server, jconn3.jar for Sybase). Alternatively you can use the mariadb
                 JDBC Driver for MySQL and the jTDS JDBC Driver for MS SQL Server and Sybase which is provided. -->
    
            <!-- Select the path to JDBC Driver -->
            <entry key="connector" value="/usr/share/java/mariadb-connector-java.jar"/>
    
            <!-- Only for DB2: Select the path to DB2 license file for JDBC Driver -->
            <entry key="connectorLicense" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="reportingDatabase">
        <userInput>
            <!-- Reporting Database Configuration
                 NOT SUPPORTED FOR SYBASE AND DB2 -->
    
            <!-- Set 'yes' if the JobScheduler and the Reporting database are the same.
                 If 'yes' then further Reporting database variables are ignored. -->
            <entry key="sameDbConnection" value="yes"/>
    
            <!-- Choose the database management system. Supported values are 'mysql' for MySQL,
                 'oracle' for Oracle, 'mssql' for MS SQL Server, 'pgsql' for PostgreSQL. -->
            <entry key="reporting.databaseDbms" value="mysql"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="reportingDbconnection">
        <userInput>
            <!-- Reporting Database Configuration
                 NOT SUPPORTED FOR SYBASE AND DB2 -->
    
            <!-- Enter the name or ip address of the database host -->
            <entry key="reporting.databaseHost" value="qnet-jobscheduler.c58hqrvwigfw.ap-northeast-1.rds.amazonaws.com"/>
    
            <!-- Enter the port number for the database instance. Default ports are for MySQL 3306,
                 Oracle 1521, MS SQL Server 1433, postgreSQL 5432. -->
            <entry key="reporting.databasePort" value="3306"/>
    
            <!-- Enter the schema -->
            <entry key="reporting.databaseSchema" value="jobscheduler_data"/>
    
            <!-- Enter the user name for database access -->
            <entry key="reporting.databaseUser" value="reporting.databaseUser"/>
    
            <!-- Enter the password for database access -->
            <entry key="reporting.databasePassword" value="reporting.databasePassword"/>
    
            <!-- You have to provide the MySQL or MS SQL Server JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL and MS SQL Server JDBC Drivers
                 are not provided. Alternatively you can use the mariadb JDBC Driver for MySQL and the
                 jTDS JDBC Driver for MS SQL Server which is provided. -->
    
            <!-- You can choose between 'yes' or 'no' for using the jTDS JDBC Driver
                 This entry affects only MS SQL Server -->
            <entry key="reporting.connectorJTDS" value="yes"/>
    
            <!-- You can choose between 'yes' or 'no' for using the mariadb JDBC Driver
                 This entry affects only MySQL -->
            <entry key="reporting.connectorMaria" value="yes"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="reportingJdbc">
        <userInput>
            <!-- Reporting Database Configuration
                 NOT SUPPORTED FOR SYBASE AND DB2 -->
    
            <!-- Configuration for JDBC Driver
                 This entry is only necessary if the package 'Database Support' is chosen and you
                 selected a DBMS type MySQL or MS SQL Server in the previous
                 <userInput> element. -->
    
            <!-- You have to provide the MySQL or MS SQL Server JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL and MS SQL Server JDBC Drivers are
                 not provided. Specify the JDBC Driver source (e.g. mysql-connector-java-*.jar for MySQL,
                 sqljdbc.jar for MS SQL Server). Alternatively you can use the mariadb JDBC Driver for
                 MySQL and the jTDS JDBC Driver for MS SQL Server which is provided. -->
    
            <!-- Select the path to JDBC Driver -->
            <entry key="reporting.connector" value="/usr/share/java/mariadb-connector-java.jar"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="end">
        <userInput/>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.InstallPanel id="install"/>
    <com.izforge.izpack.panels.ProcessPanel id="process"/>
    <com.izforge.izpack.panels.FinishPanel id="finish"/>
    </AutomatedInstallation>
    
    
  3. setup.sh を実行します

    ./setup.sh jobscheduler_install.xml
    

JOC インストール

  1. JobScheduler と同様に JOC をダウンロードします。

    スクリーンショット 2017-06-08 0.35.42.png

  2. joc_install.xml を修正します。

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <!--
    XML configuration file for JOC
    
    If you call the installer with this XML file then
    you accept at the same time the terms of the
    licence agreement under GNU GPL 2.0 License
    (see http://www.gnu.org/licenses/gpl-2.0.html)
    -->
    <AutomatedInstallation langpack="eng">
    <com.izforge.izpack.panels.UserInputPanel id="home">
        <userInput/>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.HTMLLicencePanel id="gpl_licence"/>
    <com.izforge.izpack.panels.TargetPanel id="target">
    
        <!-- SELECT THE INSTALLATION PATH
             It must be absolute!
             For example:
             /opt/sos-berlin.com/joc on Linux
             C:Program Filessos-berlin.comjoc on Windows -->
        <installpath>/opt/sos-berlin.com/joc</installpath>
    
    </com.izforge.izpack.panels.TargetPanel>
    <com.izforge.izpack.panels.UserInputPanel id="jetty">
        <userInput>
    
            <!-- JOC requires a servlet container such as Jetty.
                 If a servlet container already installed then you can use it.
                 Otherwise a Jetty will be installed in addition if withJettyInstall=yes.
                 You need root permissions to install JOC with Jetty. -->
            <entry key="withJettyInstall" value="yes"/>
            <entry key="jettyPort" value="4446"/>
            <!-- Only necessary for Windows -->
            <entry key="jettyStopPort" value="40446"/>
            <!-- Only necessary for Unix (root permissions required) -->
            <entry key="withJocInstallAsDaemon" value="yes"/>
            <!-- Path to Jetty base directory
                 For example:
                 /homer/[user]/sos-berlin.com/joc on Linux
                 C:ProgramDatasos-berlin.comjoc on Windows -->
            <entry key="jettyBaseDir" value="/home/joc/jetty"/>
    
            <!-- Java options for Jetty. -->
            <!-- Initial memory pool (-Xms) in MB -->
            <entry key="jettyOptionXms" value="128"/>
            <!-- Maximum memory pool (-Xmx) in MB -->
            <entry key="jettyOptionXmx" value="512"/>
            <!-- Thread stack size (-Xss) in KB -->
            <entry key="jettyOptionXss" value="4000"/>
            <!-- Further Java options -->
            <entry key="jettyOptions" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="reportingDatabase">
        <userInput>
            <!-- Reporting Database Configuration -->
    
            <!-- Choose the database management system. Supported values are 'mysql' for MySQL,
                 'oracle' for Oracle, 'mssql' for MS SQL Server, 'pgsql' for PostgreSQL. -->
            <entry key="reporting.databaseDbms" value="mysql"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="reportingDbconnection">
        <userInput>
            <!-- Reporting Database Configuration -->
    
            <!-- Enter the name or ip address of the database host -->
            <entry key="reporting.databaseHost" value="qnet-jobscheduler.c58hqrvwigfw.ap-northeast-1.rds.amazonaws.com"/>
    
            <!-- Enter the port number for the database instance. Default ports are for MySQL 3306,
                 Oracle 1521, MS SQL Server 1433, postgreSQL 5432. -->
            <entry key="reporting.databasePort" value="3306"/>
    
            <!-- Enter the schema -->
            <entry key="reporting.databaseSchema" value="jobscheduler_data"/>
    
            <!-- Enter the user name for database access -->
            <entry key="reporting.databaseUser" value="qnet_admin"/>
    
            <!-- Enter the password for database access -->
            <entry key="reporting.databasePassword" value="7sYne7aFEsFSK7xh"/>
    
            <!-- You have to provide the MySQL or MS SQL Server JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL and MS SQL Server JDBC Drivers are
                 not provided. Alternatively you can use the mariadb JDBC Driver for MySQL and
                 the jTDS JDBC Driver for MS SQL Server which is provided. -->
    
            <!-- You can choose between 'yes' or 'no' for using the jTDS JDBC Driver
                 This entry affects only MS SQL Server -->
            <entry key="reporting.connectorJTDS" value="yes"/>
    
            <!-- You can choose between 'yes' or 'no' for using the mariadb JDBC Driver
                 This entry affects only MySQL -->
            <entry key="reporting.connectorMaria" value="yes"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="reportingJdbc">
        <userInput>
            <!-- Reporting Database Configuration -->
    
            <!-- Configuration for JDBC Driver
                 This entry is only necessary if you selected a DBMS type such as MySQL and
                 MS SQL Server in the previous <userInput> element. -->
    
            <!-- You have to provide the MySQL or MS SQL Server JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL and MS SQL Server JDBC Drivers are
                 not provided. Specify the JDBC Driver source (e.g. mysql-connector-java-*.jar for MySQL,
                 sqljdbc.jar for MS SQL Server). Alternatively you can use the mariadb
                 JDBC Driver for MySQL and the jTDS JDBC Driver for MS SQL Server which is provided. -->
    
            <!-- Select the path to JDBC Driver -->
            <entry key="reporting.connector" value="/usr/share/java/mariadb-connector-java.jar"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="database">
        <userInput>
            <!-- JobScheduler Database Configuration -->
    
            <!-- Set 'yes' if the Reporting and the JobScheduler database are the same.
                 If 'yes' then further JobScheduler database variables are ignored. -->
            <entry key="sameDbConnection" value="yes"/>
    
            <!-- Choose the database management system. Supported values are 'mysql' for MySQL,
                 'oracle' for Oracle, 'mssql' for MS SQL Server, 'pgsql' for PostgreSQL,
                 'db2' for DB2 and 'sybase' for Sybase. -->
            <entry key="databaseDbms" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="dbconnection">
        <userInput>
            <!-- JobScheduler Database Configuration -->
    
            <!-- Enter the name or ip address of the database host -->
            <entry key="databaseHost" value="qnet-jobscheduler.c58hqrvwigfw.ap-northeast-1.rds.amazonaws.com"/>
    
            <!-- Enter the port number for the database instance. Default ports are for MySQL 3306,
                 Oracle 1521, MS SQL Server 1433, postgreSQL 5432, DB2 50000, Sybase 5000. -->
            <entry key="databasePort" value="3306"/>
    
            <!-- Enter the schema -->
            <entry key="databaseSchema" value="jobscheduler_data"/>
    
            <!-- Enter the user name for database access -->
            <entry key="databaseUser" value="qnet_admin"/>
    
            <!-- Enter the password for database access -->
            <entry key="databasePassword" value="7sYne7aFEsFSK7xh"/>
    
            <!-- You have to provide the MySQL, MS SQL Server, Sybase or DB2 JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL, Sybase and MS SQL Server JDBC Drivers are
                 not provided. Alternatively you can use the mariadb JDBC Driver for MySQL and
                 the jTDS JDBC Driver for MS SQL Server and Sybase which is provided. -->
    
            <!-- You can choose between 'yes' or 'no' for using the jTDS JDBC Driver
                 This entry affects only MS SQL Server or Sybase -->
            <entry key="connectorJTDS" value="yes"/>
    
            <!-- You can choose between 'yes' or 'no' for using the mariadb JDBC Driver
                 This entry affects only MySQL -->
            <entry key="connectorMaria" value="yes"/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="jdbc">
        <userInput>
            <!-- JobScheduler Database Configuration -->
    
            <!-- Configuration for JDBC Driver
                 This entry is only necessary if you selected a DBMS type such as MySQL,
                 MS SQL Server, Sybase ot DB2 in the previous <userInput> element. -->
    
            <!-- You have to provide the MySQL, MS SQL Server, Sybase or DB2 JDBC driver respectively if you selected
                 corresponding DBMS type. For license reasons MySQL and MS SQL Server JDBC Drivers are
                 not provided. Specify the JDBC Driver source (e.g. mysql-connector-java-*.jar for MySQL,
                 sqljdbc.jar for MS SQL Server, jconn3.jar for Sybase). Alternatively you can use the mariadb
                 JDBC Driver for MySQL and the jTDS JDBC Driver for MS SQL Server and Sybase which is provided. -->
    
            <!-- Select the path to JDBC Driver -->
            <entry key="connector" value="/usr/share/java/mariadb-connector-java.jar"/>
    
            <!-- Only for DB2: Select the path to DB2 license file for JDBC Driver -->
            <entry key="connectorLicense" value=""/>
    
        </userInput>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.UserInputPanel id="end">
        <userInput/>
    </com.izforge.izpack.panels.UserInputPanel>
    <com.izforge.izpack.panels.InstallPanel id="install"/>
    <com.izforge.izpack.panels.ProcessPanel id="process"/>
    <com.izforge.izpack.panels.FinishPanel id="finish"/>
    </AutomatedInstallation>
    
  3. setup.sh を実行します

    ./setup.sh joc_install.xml
    

続きを読む

Amazon SESで受信したメールを転送したい

前置き

SESで受信する特定のアドレス宛のメールを外部の別アドレスへ転送したい。SESの設定でこのへんをポチポチっとやれば…と思ったが、できない。できそうでできない。
そう、SESの標準機能ではメールの転送はできない。

対応方針

思い当たる案は2つ。
1. 受信メッセージをS3へ出力し、それをLambdaで拾って別アドレスへ送信する案
2. Amazon WorkMailを使う案
Lambdaを使う案が主流かもしれないが、自分の場合は品質とスピードを重視して結果的には案2を採用した。

案1 S3への出力とLambdaを使う

SESの受信ルールで、受信メッセージをS3へ出力し、それをLambdaでどうにかして別アドレスへメール送信するのが定石のようだ。

こういった情報を参考にプログラムを作成することになるのだが、文字コードや添付ファイルの有無、メッセージを編集してから転送したい、などいろいろ考慮していくとMIMEの深遠なる世界に迷い込んでなかなか大変なことになる。

加えて、SESのS3アクションで「Encrypt Message」オプションを有効化するとS3に格納するメッセージデータが暗号化されるが、それをLambda(Python)側で複合する方法がわからずハマった。というか今も解決できていない。(KMSも絡んで話が長くなるのでこれについてはあらためて別の記事にしたい。)
公式ドキュメント

スクリプト

lambda_function.py
# -*- coding: utf-8 -*-
import boto3
import json
import re
import os

#転送先アドレス(カンマ区切りで複数指定)
FORWARD_TO = os.environ['forward_to'].split(",")

#メール保存先バケット名
S3_BUCKET = "s3-bucket-name"

#転送メールの送信元ドメイン名
DOMAIN_NAME = "hogehoge.jp"

s3  = boto3.client('s3')
ses = boto3.client('ses', region_name="us-east-1")

def lambda_handler(event, context):
    #本来の送信者
    MAIL_SOURCE = event['Records'][0]['ses']['mail']['source']

    #転送メールの送信者
    MAIL_FROM = MAIL_SOURCE.replace('@','=') + "@" + DOMAIN_NAME

    #メール保存先のフォルダ名
    S3_OBJECT_PREFIX = event['Records'][0]['ses']['receipt']['recipients'][0].split("@")[0] + "/"

    #S3上のメールファイル
    s3_key = S3_OBJECT_PREFIX + event['Records'][0]['ses']['mail']['messageId']

    #メールファイル取得
    try:
        response = s3.get_object(
            Bucket = S3_BUCKET,
            Key    = s3_key
        )
    except Exception as e:
        raise e

    #メールヘッダの書き換え
    try:
        replaced_message = response['Body'].read().decode('utf-8')
        replaced_message = re.sub("\nTo: .+?\n", "\nTo: %s\n" % ", ".join(FORWARD_TO), replaced_message,1)
        replaced_message = re.sub("\nFrom: .+?\n", "\nFrom: %s\n" % MAIL_FROM, replaced_message,1)
        replaced_message = re.sub("^Return-Path: .+?\n", "Return-Path: %s\n" % MAIL_FROM, replaced_message,1)
    except Exception as e:
        raise e

    #メール送信
    try:
        response = ses.send_raw_email(
            Source = MAIL_FROM,
            Destinations= FORWARD_TO ,
            RawMessage={
                'Data': replaced_message
            }
        )
    except Exception as e:
        raise e

send_raw_emailを使い、受信したメールのBODYをそのまま送信するのであれば比較的シンプルに実装できそうだ。ただし、一般的なメール転送でよくあるような、メール本文の冒頭に元メールのヘッダ情報を追記するようなことは行っていないため、元メールの送信者がわからないという問題がある。メール本文を編集しようとするとsend_emailを使えば良さそうだが、文字コードやHTMLメール、添付ファイルの考慮など、前述のとおりディープな世界へ足を踏み入れることになり、大変。なのでここでは諦めてシンプルに。

FORWARD_TO = os.environ['forward_to'].split(",")
転送先のメールアドレスはLambdaの環境変数を使って設定する。カンマ区切りの文字列で複数を指定することも可能にしている。

MAIL_FROM = MAIL_SOURCE.replace('@','=') + "@" + domain_name
ここでひと工夫。元メールの差出人アドレスを転送メールの差出人アドレス内に埋め込む。

S3_OBJECT_PREFIX = event['Records'][0]['ses']['receipt']['recipients'][0].split("@")[0] + "/"
SESがS3へ出力する際のPrefixに合わせていれば何でも良いが、ここでは受信アカウントをPrefixとしている。

案2 Amazon WorkMailを使う

月額4USDのコストが許容できるのであればSESの受け口としてWorkMailが使える。WorkMailには転送機能がある。

WorkMailのセットアップ方法は以下がわかりやすい。
AWS WorkMailを使ってみたら想像以上に便利だった

WorkMailでの転送設定は以下(英語の公式ドキュメント)を参考に。
How do I set up an email forwarding rule in Amazon WorkMail?

Lambdaで実現できるはずの機能を月額4USDで逃げるのはエンジニアとして負けた気がしてしまうが・・・スピードと確実性を優先するならアリかと。

続きを読む

AWS Fargateのタスクでsshd、コンテナにシェルで入ってみる

とりあえずやってみたくなるやつ。

今回使用したDockerイメージのソース、タスク用のJSONなどはこちら。

一回ログインしたらセッション切断時にタスクも終了する(sshdが-dで起動している)ようにしています。

ログインしてみる

ssh root@52.90.198.xxx で。環境変数ROOT_PWをrun-taskで指定していなければパスワードroooootで。

一応amazonlinuxをベースにしており、ログインしたときの環境変数はこんな感じ。 (※ 元の環境変数からAWS_*のみ自動でexportするように指定済。)

$ ssh root@34.236.216.xxx
root@34.236.216.xxx's password: 
There was 1 failed login attempt since the last successful login.
debug1: PAM: reinitializing credentials
debug1: permanently_set_uid: 0/0

Environment:
  USER=root
  LOGNAME=root
  HOME=/root
  PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/aws/bin
  MAIL=/var/mail/root
  SHELL=/bin/bash
  SSH_CLIENT=xxxxxxxxx 57401 22
  SSH_CONNECTION=xxxxxxxxx 57401 10.0.1.188 22
  SSH_TTY=/dev/pts/0
  TERM=xterm-256color
  AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/2bf9b4ff-70a8-4d84-9733-xxxxxxx
  AWS_DEFAULT_REGION=us-east-1
  AWS_REGION=us-east-1

-bash-4.2# 

普通のメタデータは取れません。

# ec2-metadata 
[ERROR] Command not valid outside EC2 instance. Please run this command within a running EC2 instance.

Task Roleの取得はOK

-bash-4.2# curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | jq .
{
  "RoleArn": "arn:aws:iam::xxxxxxxx:role/ecsTaskExecutionRole",
  "AccessKeyId": "ASIAXXXXXXXXX",
  "SecretAccessKey": "XXXXXXXXXX",
  "Token": "xxx==",
  "Expiration": "2017-12-04T11:37:56Z"
}

という程度の軽い動作チェック程度には使えるというのと、一応このまま何かを頑張って構築しても稼働はするはず。。※コンテナポートはtask-definitionに追加で。
ホストのファイルシステムをマウントとかできないのであまり探るところもないけれど、気になるところは各自でチェックという感じで。

ふろく: AWS-CLIでrun-task

CLIの実行例にTask Roleはつけていないので、必要ならregister-task-definitionか、run-taskのoverrideで付与します。

task-definitionを作成する。

$ EXEC_ROLE_ARN=<ロールのARN> # AmazonECSTaskExecutionRolePolicyが付いたなにかしらのRole
$ aws ecs register-task-definition 
  --execution-role-arn $EXEC_ROLE_ARN 
  --family ssh-login 
  --network-mode awsvpc 
  --requires-compatibilities FARGATE 
  --cpu 256 
  --memory 512 
  --container-definitions "`cat example/task-def-sshlogin.json`"

> {
>    "taskDefinition": {
>        "taskDefinitionArn": "arn:aws:ecs:us-east-1:xxxxxxxx:task-definition/ssh-login:2",
>        "containerDefinitions": [
...

taskを実行する。

$ CLUSTER_NAME=<クラスタの名前>
$ SUBNET_ID=<コンテナを実行するサブネット>
$ SG_ID=<TCP/22が開いているSG>
$ aws ecs run-task 
  --cluster $CLUSTER_NAME 
  --launch-type FARGATE 
  --task-definition ssh-login:1 
  --network-configurationawsvpcConfiguration="{subnets=[$SUBNET_ID],securityGroups=[$SG_ID],assignPublicIp=ENABLED}"

#=> タスクのIDが返ってくるで変数に入れておくと良い

ENIのPublicIPはrun-taskの時点ではすぐにわからないので、ENIのIDがわかるまで適当にdescribe。

$ TASK_ARN=arn:aws:ecs:us-east-1:xxxxxxxxxxx:task/677c0132-9086-4087-bd18-xxxxxxxxxxx
$ aws ecs --region us-east-1 describe-tasks 
  --cluster $CLUSTER_NAME 
  --tasks arn:aws:ecs:us-east-1:xxxxxxxxxxx:task/677c0132-9086-4087-bd18-xxxxxxxxxxx

# =>
...

                {
                    "id": "xxxxxxxxxxx",
                    "type": "ElasticNetworkInterface",
                    "status": "ATTACHED",
                    "details": [
                        {
                            "name": "subnetId",
                            "value": "subnet-xxxxxxx"
                        },
                        {
                            "name": "networkInterfaceId",
                            "value": "eni-xxxxxxxx"
                        },
...

ENIのIDが分かったらPublicIPをとります。

$ aws ec2 --region us-east-1 describe-network-interfaces --network-interface-ids eni-xxxxxx | jq .NetworkInterfaces[].Association.PublicIp
"52.90.198.xxx"

ENIあたりはManagementConsole見ながらのほうが楽ですけどね。

続きを読む

Microsoft GraphでOAuthのアクセストークンを更新する仕組みを作る

背景

MicrosoftのOffice365にあるExcel OnlineのシートをAPI経由で更新したい
->そのためには当然Office365へのログイン(認証)が必要
さらに、一度の認証だけでは足りなくて、認証を維持する仕組みが必要(理由は後述)

そこで今回はOAuth認証からトークンの更新をする仕組みの作り方について書きます。

この記事の内容

この記事では
1.Microsoft Graph(Office365を触るためのAPI?)のアプリ作成からOAuth認証の方法
2.アクセストークンの取得方法
3.期限が切れた場合のアクセストークンの更新方法
をまとめます。

前提・環境

Oauth認証部分にはPython3系とflask
処理にはAWSのLambda(Python2.7)を、データの保存にはAWSのDynamoDBを使ってます

OAuth認証の仕組みはざっくり知ってる程度の知識で書いていますので、手順が間違っていたり・飛ばしていたりする可能性があります。その際はご指摘いただけるとありがたいです。

1.アプリの作成からOAuth認証まで

1-1.アプリの登録

アプリを Azure AD v2.0 エンドポイントに登録する
このページを参考にアプリを新規登録して、「アプリケーション ID」と「アプリケーション シークレット」をメモしておきます。

ここでの注意点

↓Microsoft Graph のアクセス許可で必ずoffline_accessをスコープに入れること
これをスコープに入れないと後述するrefresh tokenが返って来ません。
(refresh tokenが無いとアクセストークンの有効期限が切れるたびに手作業(ブラウザ)での認証が必要になる?)

アクセス許可

1-2.OAuth認証

Microsoft Graph アプリの構築を開始する
こちらのページで各言語でのサンプルソースが置いてあります。

今回はPythonを使いましたのでこちらです。

認証までの流れ

GithubのREADMEに記載している流れそのままですが

  • 上のソースをgit cloneしてソースを持って来る
  • 必要なパッケージをpipでインストール
  • READMEに書いている設定↓をして
  • flaskを実行してローカルにWebサーバーを立てる
  • アクセスしてOAuth認証の動作確認

といった感じです。
解説するまでもないかもしれませんが、一つずつ見ていきます。

1.git cloneする

git clone https://github.com/microsoftgraph/python3-connect-rest-sample.git

2.必要なモジュールをpipでインストール(オプション)

もしも5まで進めて no module named ~~といったエラーが出た場合は
pip intall ~~で必要なモジュールを入れてください。

3.READMEに書いている設定をする

README和訳
1.好きなエディタで_PRIVATE.txtを開いて
2.ENTER_YOUR_CLIENT_IDという文字の部分を1-1でメモしたアプリケーション IDに書き換えて
3.ENTER_YOUR_SECRETという文字の部分を1-1でメモしたアプリケーションシークレットに書き換えて

4.flaskを実行してローカルにWebサーバーを立てる

README和訳
4.python manage.py runserver ってコマンド打ってね
5.http://localhost:5000/ にアクセスしたらOAuth認証するためのページが開くよ

5.動作確認

http://localhost:5000/ にアクセスした時にこんな画面が出たら、起動は成功です。
flask

認証をした後は↓こんな画面になって
認証後

Send mailボタンを押した時にメールが飛んでくればOAuth認証成功です。

2.アクセストークンの取得

ここまででOAuth認証は成功しました。
ここからはその認証情報を維持する方法を書いていきます。

上のflaskのソース内にアクセストークンを使っている場所があるので、そこでprintをしてaccess_tokenとrefresh_tokenを可視化します。

connectsample.py 内の80行目あたりに下記を追加するとターミナルに二つのトークンが表示されますので、メモしておきます。

add_to_connectsample
print(response['access_token'])
print(response['refresh_token'])

3.期限が切れた場合のアクセストークンの更新方法

アクセストークンには有効期限があります。その有効期限が切れた後は、アクセストークンの更新が必要です。

更新トークンを使用して新しいアクセス トークンを取得する

今回はアクセストークンとリフレッシュトークンをDynamoDBに保存しておいて、利用時に更新&取り出しを行うようにしました。

3-1.DynamoDBの準備

DynamoDB
こんな感じでテーブルを作っておきます。(usernameと書いたつもりがusenameになってました。)
andoとなっているところは一意に判定できる名前を入れておいてください。

access_token と refresh_tokenには上で取得したものを保存しておいてください。

3-2.Lambdaの準備

getaccess_token.py
# coding:utf-8
import os
import boto3
import json
from boto3.dynamodb.conditions import Key, Attr
import requests

client_id = 'アプリケーション ID'
client_secret = 'アプリケーション シークレット'
username = 'ando'

def get_access_token():
    dynamodb = boto3.resource('dynamodb')
    table    = dynamodb.Table('excelOnlineToken')

    response = table.query(
        KeyConditionExpression=Key('usename').eq(username), Limit=1)
    refresh_token = response['Items'][0]['refresh_token']
    headers = { 'Accept' : 'application/json',
                'Content-Type' : 'application/x-www-form-urlencoded'
                }

    payload = {'client_id': client_id, 'scope': 'User.Read Mail.Send offline_access Files.ReadWrite.All','refresh_token':refresh_token,'redirect_uri':'http://localhost:5000/login/authorized','grant_type':'refresh_token','client_secret':client_secret}

    response = requests.post(
        'https://login.microsoftonline.com/common/oauth2/v2.0/token',
        headers=headers,data=payload)
    jsonObj =  json.loads(response.text)


    table.update_item(
        Key={
            'usename': 'ando'
        },
        UpdateExpression='SET refresh_token = :val1,access_token = :val2',
        ExpressionAttributeValues={
            ':val1': jsonObj["refresh_token"],
            ':val2': jsonObj["access_token"]
        }
    )

    return jsonObj["access_token"]

このLambdaを実行すればMicrosoft GraphにアクセスをしてDynamo内のアクセストークン・リフレッシュトークンを更新して、最新のアクセストークンを返してくれます。

最後に

今回はアクセストークンの期限が切れているかどうかの判定をせずに毎回更新しちゃってますが、期限が切れていた場合のみ更新という風にした方が良さそうですね。

続きを読む

AWSからメンテ通知が来たらインスタンスを再起動させる(Slackメッセージボタンでの事前確認付き)

AWSから不定期に届くメンテ通知や障害メールの対応をSlackで完結させます。

完成図

できあがったものがこれです。

  • 注:このフローは実際に動くし使えます。
  • 作成所要時間 約15分
    2017-11-20 10_00_16-aws-mentenance.json - FrontOps.png

なお、定型対応フローが簡単に作れるアプリFrontOps を使っています。

はじめに

ある日、こんな件名のメールが届きます。

  • Amazon EC2 Maintenance – Maintenance
  • [Retirement Notification]

インスタンスが動いている物理サーバをメンテするから、いつまでにインスタンスを移動してください。
インスタンスの状態が異常なので、インスタンスを確認して再起動してください。

といった内容です。
このメール、たまにしか来ないことに加え、英語なこともあり他の迷惑メールや広告メールに紛れて見落とすことがよくありました。

そこで、このメールを受信したらSlackに通知させて見落としを防止することにしました。

またこのメール。メール文中にはインスタンスID の記載しかないので、インスタンス名を確認しないと影響判断ができません。
ただ、インスタンス名を確認するにはいちいちAWSのマネジメントコンソールにログインする必要があって面倒です。

そこでSlack通知の際に、インスタンス名を補足させることにします。

また、このメールの対応としてはインスタンスを再起動するしかありません。
機械的に再起動させてもいいのですが、
再起動前に事前作業が必要なインスタンスもあるので、
事前にSlackのメッセージボタンを使って承認を挟むことにします。

必要なもの

  • FrontOps (https://frontops.exhands.org)

    • 無料アカウントで、アプリをダウンロードして利用することができます
  • AWS CLIが使えてSSHでログインできるサーバ
    • あらかじめaws configureでクレデンシャル等は設定しておきます。
  • 通知先のSlackチャンネル

設定説明

全体フローは冒頭にあげた通りです。以下では各ノードの設定を説明します。

メール定期受信

メールを定期的に受信させます。1分ごとにしました。
2017-11-21 08_15_52-aws-mentenance.json - FrontOps.png

件名でフィルタ

メールの件名は、msg.topicに入ってきます。msg.topicに文字列が含まれるかどうかを設定します。
条件に該当する場合は次のノードに進みます。
条件に該当しない場合はここで処理が終わります。
2017-11-20 23_48_12-aws-mentenance.json - FrontOps.png

インスタンスID取得

メールの本文は、msg.payloadに入ってきます。
ここからJSONATAを使ってインスタンスIDを抽出して、msg.instanceIdに設定します。

$match(payload, /^(i-\w+)/m).groups

2017-11-21 07_41_19-aws-mentenance.json - FrontOps.png

インスタンス名取得

インスタンス名は、ふだん使っているサーバにSSHログインしてAWS CLIで取得します。

取得した結果は、msg.instanceNameに設定します。
コマンド部にはmustacheが使えるので、{{{プロパティ}}}msgオブジェクトのプロパティを展開できます。
ここでは先に取得したインスタンスIDを展開させています。

aws ec2 describe-instances --instance-id {{{instanceId}}} --query 'Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key==`Name`].Value]' --output text

2017-11-20 23_48_51-aws-mentenance.json - FrontOps.png

Slack通知

Slackのgeneralチャンネルに

メールの件名(msg.topic)
メールの本文(msg.payload)
インスタンス名(msg.instanceName.stdout)

を通知します。

2017-11-20 23_49_09-aws-mentenance.json - FrontOps.png

再起動実施確認

インスタンスの停止・起動をメッセージボタンで確認します。
よく使うYes/Noの形式なので、情報ウィンドウに表示されているテンプレートをコピペして文言だけ直して使います。

2017-11-20 23_49_28-aws-mentenance.json - FrontOps.png

復唱

どのメッセージボタンが押されたかを復唱させます。この情報はmsg.answerに入ってきます。
これもよくあるパターンなので、情報ウィンドウに表示されているテンプレートをコピペして使います。
2017-11-20 23_49_41-aws-mentenance.json - FrontOps.png

承認結果判定

ボタン操作の結果に応じて分岐を作ります。
2017-11-20 23_49_55-aws-mentenance.json - FrontOps.png

対応終了

2017-11-20 23_50_10-aws-mentenance.json - FrontOps.png

対応開始宣言

2017-11-20 23_50_23-aws-mentenance.json - FrontOps.png

インスタンス停止

インスタンス停止は、AWS CLIでおこないます。
インスタンスを停止させて、ステータスがstoppedになるまで待ちます。
タイムアウトは60秒にしました。
2017-11-20 23_52_15-aws-mentenance.json - FrontOps.png

インスタンス起動

インスタンス起動は、AWS CLIでおこないます。
インスタンスを起動させて、ステータスがrunningになるまで待ちます。
タイムアウトはちょっと長めに90秒にしておきます。
2017-11-21 08_09_04-aws-mentenance.json - FrontOps.png

対応完了報告

対応完了を報告させて終了します。
この前にさらに正常性確認を追加してもいいかもしれません。
2017-11-20 23_51_24-aws-mentenance.json - FrontOps.png

例外処理

タイムアウトやその他エラーが発生した時には、ログに記録を残させると同時にSlackに通知させています。
電話で通知させるパターンもよくあります。

フローデータ

以下をコピーして、[読み込み]>[クリップボード] でフローを取り込むことができます。

[{"id":"67e11027.4cc2c","type":"command","z":"bd84d76c.2d93f8","connectionconfig":"fd7512e2.f22ec","command":"aws ec2 describe-instances --instance-id {{{instanceId}}} --query 'Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key==`Name`].Value]' --output text","timer":"15","oldrc":false,"env":"","pty":false,"trim":false,"field":"instanceName","fieldType":"msg","name":"インスタンス名取得","x":190,"y":240,"wires":[["b0addf6c.85c41"]]},{"id":"10686c8e.7ab4d3","type":"e-mail in","z":"bd84d76c.2d93f8","name":"メール定期受信","protocol":"IMAP","server":"imap.gmail.com","useSSL":true,"port":"993","box":"INBOX","disposition":"Read","repeat":"60","x":130,"y":160,"wires":[["20849217.8d4fae"]]},{"id":"20849217.8d4fae","type":"switch","z":"bd84d76c.2d93f8","name":"件名でフィルタ","property":"topic","propertyType":"msg","rules":[{"t":"cont","v":"Amazon EC2 Maintenance - Maintenance","vt":"str"},{"t":"cont","v":"[Retirement Notification]","vt":"str"}],"checkall":"true","outputs":2,"x":310,"y":160,"wires":[["dfb73593.bb2f08"],["dfb73593.bb2f08"]]},{"id":"dfb73593.bb2f08","type":"change","z":"bd84d76c.2d93f8","name":"インスタンスID取得","rules":[{"t":"set","p":"instanceId","pt":"msg","to":"$match(payload, /^(i-\\w+)/m).groups[0]\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":160,"wires":[["67e11027.4cc2c"]]},{"id":"b0addf6c.85c41","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"*{{{topic}}}*\n\n```\n{{{payload}}}\n```\n{{{instanceName.stdout}}}","output":"str","gonext":true,"name":"Slack通知","x":380,"y":240,"wires":[["f6e76317.5bd3"]]},{"id":"f6e76317.5bd3","type":"slack-ask","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"{\n  \"text\": \"インスタンスを停止・起動していいですか?\",\n  \"attachments\": [\n    {\n      \"title\": \"承認依頼\",\n      \"text\": \"実行してよろしいですか\",\n      \"fallback\": \"このクライアントでは返答できません\",\n      \"callback_id\": \"approve_123\",\n      \"color\": \"#3AA3E3\",\n      \"attachment_type\": \"default\",\n      \"actions\": [\n        {\n          \"name\": \"answer\",\n          \"text\": \"承認\",\n          \"value\": \"yes\",\n          \"type\": \"button\",\n          \"style\": \"primary\"\n        },\n        {\n          \"name\": \"answer\",\n          \"text\": \"否認\",\n          \"value\": \"no\",\n          \"type\": \"button\"\n        }\n      ]\n    }\n  ]\n}","timeout":"30","timeoutUnits":"minutes","name":"再起動実施確認","x":200,"y":300,"wires":[["225facba.2a5cf4"]]},{"id":"225facba.2a5cf4","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"> {{{payload.original_message.text}}}\n{{{answer.value}}} by {{{payload.user.name}}}","output":"str","gonext":true,"name":"復唱","x":370,"y":300,"wires":[["75b36d.ac831c94"]]},{"id":"75b36d.ac831c94","type":"switch","z":"bd84d76c.2d93f8","name":"承認結果判定","property":"answer.value","propertyType":"msg","rules":[{"t":"eq","v":"no","vt":"str"},{"t":"eq","v":"yes","vt":"str"}],"checkall":"true","outputs":2,"x":540,"y":300,"wires":[["eb240803.79d998"],["351ce544.8e522a"]]},{"id":"3c6efba0.b994e4","type":"catch","z":"bd84d76c.2d93f8","name":"例外処理","scope":["b0addf6c.85c41","75b36d.ac831c94","dfb73593.bb2f08","cd9f1df.a3aede","67e11027.4cc2c","c274bafd.cddfe8","20849217.8d4fae","f6e76317.5bd3","10cc5de3.b72572","eb240803.79d998","351ce544.8e522a","225facba.2a5cf4","b4948d24.29e29"],"x":160,"y":460,"wires":[["a640fbbb.d2ee18","25f25187.2b4cbe","6fdbe105.9ed53"]]},{"id":"eb240803.79d998","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"後の対応をお願いします。","output":"str","gonext":false,"name":"対応終了","x":800,"y":300,"wires":[]},{"id":"351ce544.8e522a","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"対応を始めます","output":"str","gonext":true,"name":"対応開始宣言","x":200,"y":360,"wires":[["cd9f1df.a3aede"]]},{"id":"cd9f1df.a3aede","type":"command","z":"bd84d76c.2d93f8","connectionconfig":"fd7512e2.f22ec","command":"aws ec2 stop-instances --instance-id {{{instanceId}}}\naws ec2 wait instance-stopped --instance-id {{{instanceId}}}","timer":"60","oldrc":false,"env":"","pty":false,"trim":false,"field":"payload","fieldType":"msg","name":"インスタンス停止","x":400,"y":360,"wires":[["b4948d24.29e29","c274bafd.cddfe8"]]},{"id":"b4948d24.29e29","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"インスタンスを停止しました。\nインスタンスを起動します。","output":"str","gonext":false,"name":"途中報告","x":600,"y":400,"wires":[]},{"id":"c274bafd.cddfe8","type":"command","z":"bd84d76c.2d93f8","connectionconfig":"fd7512e2.f22ec","command":" aws ec2 start-instances --instance-id {{{instanceId}}}\naws ec2 wait instance-running --instance-id {{{instanceId}}}","timer":"90","oldrc":false,"env":"","pty":false,"trim":false,"field":"payload","fieldType":"msg","name":"インスタンス起動","x":620,"y":360,"wires":[["10cc5de3.b72572"]]},{"id":"10cc5de3.b72572","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"インスタンスを起動しました。\n対応が完了しました。","output":"str","gonext":false,"name":"対応完了報告","x":820,"y":360,"wires":[]},{"id":"a640fbbb.d2ee18","type":"slack-say","z":"bd84d76c.2d93f8","slack":"","channel":"general","msg":"異常が発生しました。対応をお願いします。\n{{{error.message}}}\n```\n{{{payload}}}\n```\n","output":"str","gonext":false,"name":"異常発生連絡","x":400,"y":460,"wires":[]},{"id":"25f25187.2b4cbe","type":"debug","z":"bd84d76c.2d93f8","name":"","active":true,"console":"false","complete":"true","x":370,"y":560,"wires":[]},{"id":"6fdbe105.9ed53","type":"file","z":"bd84d76c.2d93f8","name":"ログ追記","filename":"c:\\Temp\\aws-mentenance.log","appendNewline":true,"createDir":true,"overwriteFile":"false","x":377.1000061035156,"y":510.20001220703125,"wires":[]},{"id":"d20cc86f.3c0dd8","type":"comment","z":"bd84d76c.2d93f8","name":"AWSのメンテ通知の対応","info":"","x":140,"y":100,"wires":[]},{"id":"fd7512e2.f22ec","type":"connection","z":"","name":"util server","host":"35.185.201.106","username":"sakazuki"}]

最後に

FrontOps を使うと、複数のツール・ソフトを簡単につなげて制御することができます。
少人数でシステム運用・保守をされている方に是非活用いただきたいアプリです。

続きを読む