Amazon lexで音声チャットボットを作成してみた

天気の良い週末にやることがなく、昼の12時ごろ、趣味の長時間歯磨きをしながらtechCrunchを見ていたら、amazonのlexの記事が乗っていた。

http://jp.techcrunch.com/2017/04/21/20170420amazon-lex-the-technology-behind-alexa-opens-up-to-developers/

Amazonの仮想アシスタントAlexaを支えているテクノロジーであるAmazon Lexが、今朝(米国時間20日)のロイターの記事によれば、プレビュー段階を終了したということだ。

↓↓↓
https://aws.amazon.com/jp/lex/

Amazon Lex は、音声やテキストを使用した会話型インターフェイスをさまざまなアプリケーションに構築するためのサービスです。
Lex では、自動音声認識 (ASR) という音声をテキストに変換するための高度な深層学習機能と、
テキストの意図を理解するための自然言語理解 (NLU) を利用できます。
これにより、非常に魅力的なサービスと生き生きとした音声対話を実現するアプリケーションを構築できます。
Amazon Lex を使うと、すべての開発者が Amazon Alexa に採用されている深層学習技術と同じ技術を利用し、
自然言語での高度な対話ボット (チャットボット) を短時間で簡単に構築できるようになります。

このページにある動画は去年の11月末のものだ。
その頃、某ゲームの開発と並行して走る運営の現場の両方が修羅場で、毎日トラブルと戦っていた。さらにトラブルは現場だけとは限らず、外野がキナ臭くなり、モヒカンと肩パッドの荒くれ供から平和を守るために北斗百烈拳を繰り出していた。明日を見失って、CV千葉繁になっていた記憶がうっすらとある。
当然re:InventのKeynoteなど見る暇はなかった。

Try a sample

まずはこちらのページでサンプルを作ってみる。
https://console.aws.amazon.com/lex/home?region=us-east-1#bot-create:

cb-1.png
BookTripをクリック。

cb-2.png

IAM:自動的に作ってくれるんはありがたい。念のため後でデタッチされた権限を見ることにするけどな。
Child-Directed?:子供専用ではないからNo。

Createを押しやす。


マイクを許可しますかのプロンプトが出るので、許可にする。
このmacには内臓マイクがないので、iPhoneのヘッドホンを繋いでおく。

cb-3.png

しかしこの後、何をどうしたらいいのか、説明がないからさっぱりわからない。。。。。。
困った。

tutorialの動画をみながらもう一度sample chat botをつくる

AWS Lex Demo Tutorial (Jan 2017)
https://www.youtube.com/watch?v=7uG9cuxNo5k
「花の注文をするチャットボット」を10分くらいで作る様子。

4ヶ月ほど前に親切な人が投稿してくれたチュートリアル動画で、
やさしい英語をえらんで説明してくれているが、
英語のききとりがやっとの自分はYoutobeの字幕機能で補いながらみた。
この通りやってみると、なんとなくわかる。

なおこの動画の中では、まだ「Channels」がFacebookしかないが、
3ヶ月後の現在は、3つに増えている。

  • Facebook
  • Twilio SMS ←増えた
  • Slack ←増えた

花屋のサンプルをそのまま真似して、以下のように作ってみた。

name:OrderFlowersSitopp
Description:Bot to order flowers on the behalf of a user
Output voice:Joey

cb-8.png

Buildをするとtest Botでtext入力できるようになる。
Sample Utterancesの1個めをコピペして、
I would like to pick up flowers
と記入してエンターしてみるとすぐ答えが返ってきた。ほうほう。
cb-7.png
あっ、これもうできちゃったのか。早いなぁ。(΄◉◞౪◟◉`)

音声入力してみる

AWSのLexのコンソールに行く
https://console.aws.amazon.com/lex/home?region=us-east-1#bots:

さっき作った「name:OrderFlowersSitopp」を選ぶcb-15.png

右側にチャットボットのタブが現れるので、
入力欄のマイク部分をクリックする。そうすると音声入力モードになった。

cb-13.png


Macに繋いだiPhone用のヘッドフォンのマイクのあたりに話しかけると、テキスト化して、チャットにしてくれます。

上の例だと、私の発音が悪いので、rosesがlosesとかclosesになっている。ひどい(笑)
しかもボットはそのまま「Ok your closes will be ready 」と予約終了してしまった。
いったいなんの花が届くのか。。笑

もちろん、これはサンプルだから何も届かないけど、
ちゃんとカスタムスロットに無いものはエラーにしないと、そのまま注文を受け取ったことになってしまうから、
注意が必要ということで。

sampleを作ってわかったこと

  • サンプルの3種類は以下の通り。主たる目的はこういうものなのかな。

    • 旅行の予約
    • 花屋のオーダー
    • 歯医者の予約

考えられる用途

  • 保険の見積もり
  • 通販サイトのパスワード忘れた
  • 美容院の予約
  • ゲームの進行不具合の通報
  • ゲームのチート疑惑の通報
  • ゲーム掲示板の炎上の通報
  • ゲームの..いかん、つい仕事が

これがFacebookのお店のページに備わってたら、確かに気楽に呼び出しやすい。
でも受け取る側はどうなんだろう? botが応答して解決しない場合もあるだろうし、ちゃんと過去の履歴を呼び出せるのかな?

大変そうな点

Sample UtterancesとSlot typesの用意が大変そう。

Sample Utterancesとは、ユーザーがこういったらこの部分をパラメタとして受け取るという設定。花屋の予約sampleでは2個しかなかったけど、こんなんで済むはずがない。

  • I would like to pick up flowers
  • I would like to order some flowers

様々な言い方を網羅しておかないと、「おっしゃることがわかりません」を連発するポンコツbotになってしまう。
(参考動画 Virgin Flight – Saturday Night Live

Slot typesは、ユーザーの返答の中から受け付けるキーワードの種類で、花屋の予約sampleでは花の種類3つが該当する。だがこんな少なくて済むはずがない。3種類しか売ってない花屋っていうのだったら別だけど。
– roses
– lilies
– tulips

Sample utterancesもSlot TypesもAlexa Skillsにも同様の設定項目があり、これらをいかにきめ細かに定義できるかでbotの優秀さが決まると思う。

まとめ

  • AWSのlexを使ってchatbotが作れる。
  • 連携先は、Facebook、Twilio SMS、slackが選べる。ただし音声入力は、声の入力装置があるプラットフォームじゃないとダメ。(Twilioは電話と連携してるから、これが候補か。)
  • Utterancesとslot typesをちゃんと定義しないと、ポンコツbotになってしまう。人工知能というより人工無能。これはAlexa skillsも同じ。
  • しゃべった言葉を分解してテキストにするところが人工知能。ここはブラックボックス。
  • サンプルでは必要なかったが、ちゃんとbotを作るときにはLambda関数も書く必要がある。(http://docs.aws.amazon.com/ja_jp/lex/latest/dg/getting-started-ex2.html)
  • botの会話ログを見るのってどうするんだろ?

以上!

続きを読む

Terraformでstate lockを触ってみた。

はじめに

初めてTerraformを触る折に
「v0.9以上だとstate lockっていう機能があるから使ったほうが良いよ」
というアドバイスをもらいました。
(そもそも、stateってなんですか?から始まるわけですが・・・)

初めてTerraformを触った時に、公式ドキュメントだけでは???な状態だったので
痕跡として残しておきます。

結果として、まだ使いこなせてません。というか僕の使い方は果たしてあっているのだろうか
なので情報としては不足しているかもしれませんが、とりあえず備忘録的に残します。
またわかったらUpdateします。

そもそもState Lockとは

完全に理解しないで書いてますが
Terraformは「自分が知っているリソース(EC2やS3)しか関与しないよ」というポリシーで
どのリソースを自分が扱ったか、というのをtfstateというJSONファイルで管理しているようです。

このtfstateファイルは、一人でTerraformを動かす場合は問題無いのですが
複数人でTerraformをいじる必要が出てきた時に問題で、それは「backend」モジュールを使うことで回避してきたようですが
同じタイミングでterraformを実施した場合、その部分までは制御しきれてなかったようです。

で、v0.9以上?から、「Plan/Applyをしているタイミングではロックする」機能が実装されたようで。
せっかくなので導入してみました。

公式サイト:https://www.terraform.io/docs/state/locking.html

準備

手動で準備が必要なものは
・terraformを実行するユーザのCredential情報
→ めんどくさかったので test-terraformとかいうユーザを別途作成しました。
・S3 Bucketの作成
→ terraform-s3-state とかいう名前で作りましょう
・DynamoDBの作成
→ 名前はなんでも良いです。terraform-state-lock とかで作りましょう。
  プライマリキーを「LockID」としてください。それ以外では動きません。
作り終えたら、読み込み・書き込み容量ユニットは最低の1にしておいたほうが良いと思います。

※ S3とDynamoDBをterraformで管理はしないほうが良さげです。
どのみち、初回実行時に「そんなリソース無いんだけど!」ってTerraformが怒ります。

動くまで

今回はState Lockの話がメインなので作るリソースはなんでも良いです。
リージョンが関係無いRoute53を題材にします。

route53.tf
# 適当に1ゾーン作ります。

resource "aws_route53_zone" "test_zone" {
    name    =   "test.lockstate.com"
}
settings.tf
# ネーミングよくないですが、providerとbackendの設定します

provider "aws" {
    access_key = "ACCESS_KEY"
    private_key = "PRIVATE_KEY"
}

terraform {
    backend "s3" {
        bucket     = "terraform-s3-state"
        key        = "terraform.tfstate"
        region     = "s3とdynamoがいるregion"
        lock_table = "terraform-state-lock"
    }
}

これで、「該当のディレクトリに移動して」$ terraform plan してください。(怒られます。)

Backend reinitialization required. Please run "terraform init".
Reason: Initial configuration of the requested backend "s3"

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

とりあえず terraform init しろよと言われるので言われるがままに。

$ terraform init

ただし、以下の通り、認証情報がねーんだけど!って怒られる。なんやねん。

Initializing the backend...

Error configuring the backend "s3": No valid credential sources found for AWS Provider.
  Please see https://terraform.io/docs/providers/aws/index.html for more information on
  providing credentials for the AWS Provider

Please update the configuration in your Terraform files to fix this error
then run this command again.

ここらへんちゃんとよくわかってないですが、この問題の対処として2つありました。
A. settings.tfのbackendにaccess_key/secret_keyの2つを渡してCredential情報を平文で書く。
-> 変数でいいじゃん!と思ったんですが、変数で渡すと、Terraformがよしなにやる「前に」
  Backendが立ち上がるために違う方法で渡してくれ、と怒られました。
以下のようなメッセージ

% terraform init 
Initializing the backend...
Error loading backend config: 1 error(s) occurred:

* terraform.backend: configuration cannot contain interpolations

The backend configuration is loaded by Terraform extremely early, before
the core of Terraform can be initialized. This is necessary because the backend
dictates the behavior of that core. The core is what handles interpolation
processing. Because of this, interpolations cannot be used in backend
configuration.

If you'd like to parameterize backend configuration, we recommend using
partial configuration with the "-backend-config" flag to "terraform init".

B. 環境変数にaccess_key/secret_keyの2つを食わせる
  → 今はこちらを採用中。

init or plan実行時に、状況に応じて「ローカルのStateをS3にコピーするか / S3からStateファイルをローカルにコピーするか」聞かれるかもしれません。
初回(もしくはテスト)であればYes、すでに何回か実行しているような状況ではnoでいいんじゃないでしょうか。

これで、terraform plan (or apply)を実行すると
DynamoDBのLockDBに対して誰が使用しているか、のロック情報が書き込まれ
終わったタイミングでリリースされます。

ターミナルやシェルを複数立ち上げ、同じぐらいのタイミングで
terraform plan( or apply )を実行すると、ロックが成功できた奴以外はエラーで落とされます。
ただし、ロックがリリースされる前の実行中などにCtrl-Cなどで強制終了させるとロックが残り続けるので注意。
$ terraform force-unlock ID情報
で強制ロック解除できます。

今僕が解決できてない問題点

公式ドキュメントを見る限り、「terraform remote コマンドは terraform initコマンドに置き換えたよ!」と言っています。
また、backendを使用する、backendを書き換えた、などのタイミングに起因して terraform init を求められます。

が、terraform initは、「実行されたディレクトリに対して .terraform/terraform.tfstate」を吐き出します。
そしてそれが無いと怒ります。
まだ試せてはいませんが、以下のようなtfの配置をしていたりした場合
root配下でinitを実行しても、tfファイルがありませんと怒られる状況で
tfファイルがあるディレクトリまで移動しないとinitができない状態です。

$ terraform init ./route53/tf
とするとroot直下にtfファイルがコピーされる状況です。
なので、リソースごとに区切る、とか環境ごとに区切る、とかはうまくできていません。
別の見方をすれば、環境ごとに一つのディレクトリでリソース類は全部その中で管理しろよ!
というHashiCorpからのお達しなのか・・・?
これは、新しく実装されたEnvironmentを試せ、ということなんでしょうか。。。

と思ったらBackendConfigなるものがあるらしいのでそれを試してみよう。

root
├── settings
│   └── tf
│   └── settings.tf
├── route53
│   └── tf
│   └── route53.tf
└── ec2
└── tf
└── ec2.tf

Terraformとの付き合い方がよくわからない

続きを読む

Terraform v0.8からTerraform v0.9へのアップグレード S3の状態管理ファイルを移行方法

Terraformをアップグレードした際にS3に保存していた状態管理ファイルも移行する必要が
あったのでその作業メモです。

Terraformのアップグレード

https://www.terraform.io/downloads.html から環境に適したファイルを
ダウンロードして、解凍して作成されるterraformファイルをパスの通ったところに
配置します。
例えば以下のようにパスを通しておきます。

bash_profile
export PATH=$PATH:${HOME}/.terraform_0.9.3_darwin_amd64

状態管理ファイルの移行

Terraformのアップグレード自体は数分で終了しますが、planコマンドを実行すると以下の
メッセージが表示されます。

$ terraform plan
Deprecation warning: This environment is configured to use legacy remote state.
Remote state changed significantly in Terraform 0.9. Please update your remote
state configuration to use the new 'backend' settings. For now, Terraform
will continue to use your existing settings. Legacy remote state support
will be removed in Terraform 0.11.

You can find a guide for upgrading here:

https://www.terraform.io/docs/backends/legacy-0-8.html

There are warnings related to your configuration. If no errors occurred,
Terraform will continue despite these warnings. It is a good idea to resolve
these warnings in the near future.

どうやら、Terraformがv0.9になったタイミングで状態管理ファイルの管理方法が変更になったようです。
メッセージを見る限りではv0.11までは変更しなくても使えそうな感じですが、
https://www.terraform.io/docs/backends/legacy-0-8.html ではv0.10になったタイミングで
移行作業が必要になりそうなので、上記URLの手順に沿って新しい状態管理ファイルに移行してみます。

既存の状態管理ファイルの取得

最新の状態管理ファイルをremoteから取得します。

$terraform remote pull

ローカルの状態管理ファイルのバックアップ

.terraform/terraform.tfstateファイルをterraformのプロジェクトの外に置きます。

$cp .terraform/terraform.tfstate /tmp/

backendの設定の追加

backendの設定ファイルを作成します。
今までもS3に状態管理ファイルを置いていたので以下のURLを参考にしながら
backend.tfファイルを作成しました。
https://www.terraform.io/docs/backends/types/s3.html

backend.tf
terraform {
  backend "s3" {
    bucket = "terraform-state"
    key    = "tf"
    region = "ap-northeast-1"
  }
}

initコマンドの実行

backendの初期化コマンドを実行します。

$ terraform init -backend=true -force-copy  -lock=false
Initializing the backend...
New backend configuration detected with legacy remote state!

Terraform has detected that you're attempting to configure a new backend.
At the same time, legacy remote state configuration was found. Terraform will
first configure the new backend, and then ask if you'd like to migrate
your remote state to the new backend.




Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your environment. If you forget, other
commands will detect it and remind you to do so if necessary.

上記のようなメッセージが表示され、移行が無事に行われたようです。

terraform planで動作確認

最後にterraform planで動作確認をしてみます。
Deprecation warningが消えて通常の状態に戻りました。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
...

続きを読む

Let’s Encrypt を使った SSL 設定 for Amazon Linux

SSLのサイトを作ってみたくなった

ちょっとした無償サービスを作るにあたって、Let’s Encrypt を使った SSL を試しに使ってみたくてやってみました。
環境は、Amazon Linux。

手順

$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto
$ ./certbot-auto
(中略)
FATAL: Amazon Linux support is very experimental at present...
if you would like to work on improving it, please ensure you have backups
and then run this script again with the --debug flag!
Alternatively, you can install OS dependencies yourself and run this script
again with --no-bootstrap.

Amazon Linux は、–debug をつけろというのでつけました。

$ ./certbot-auto --debug
(中略)
The following errors were reported by the server:

Domain: XXX.XXX.jp
Type:   connection
Detail: Failed to connect to XXX.XXX.XXX.XXX:443 for tls-sni-01 challenge
(後略)

よくよく調べると以下に該当。

https://letsencrypt.jp/usage/dvsni-challenge-error.html

セキュリティグループに、443を指定するのを忘れていたので指定して、コンソソールからインスタンス再起動。

$ ./certbot-auto renew

インストールが終わったら、httpd を再起動。

$ sudo service httpd graceful

https でアクセスして完了を確認。

参考

https://letsencrypt.jp/
http://qiita.com/takahiko/items/a08895550727b95b6c36
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/SSL-on-an-instance.html#ssl_enable

続きを読む

lammaで始める等身大のAWS Lambda

(https://medium.com/@ayemos/lamma%E3%81%A7%E5%A7%8B%E3%82%81%E3%82%8B%E7%AD%89%E8%BA%AB%E5%A4%A7%E3%81%AEaws-lambda-d032addc5f4d より転載)

TL;DR

AWS Lambda

Amazon S3上にcsvファイルがアップロードされたら、その集計データをS3上の別のcsvファイルに保存したい

毎週金曜日の21:00に特定のタグがついたEc2インスタンスの過去数時間のCPUUtilization値をチェックして、アイドル状態にあるものは自動的にシャットダウンしたい

などなど、AWS上に構築されたインフラシステムにおいて、あるトリガーが発生したときに、ちょっとしたロジックと実装でもって処理を継続したい、というシチュエーションに最適なサービスがAWS Lambdaです。

AWS Lambdaで予め用意された計算環境(python, Node.jsなどの言語環境と、 AWS SDKの各言語実装などが含まれる)を利用することで、EC2上に計算環境をプロビジョンする手間が省けたり、実行回数とその長さに基づいた課金システムで、非常に安価に利用できるなどの利点があります。

しかし、計算機環境の構築と付き合わなくなった一方で、実装された関数のLambda上でのバージョンコントロールやデプロイ/ロールバックが面倒であるという現状があり、実際apexやlamveryなど、Lambdaのマネジメントツールが開発されています。

lamma

https://github.co/ayemos/lamma
ruby製のAWS Lambdaのマネジメントツール。

すでにあるツールとの差別化という意味で「IoT、ChatBotなどの目的でAWS Lambdaをなるべく手軽に今すぐ使いたい」という人をターゲットに作っているつもりです。

$ gem install lamma
$ lamma init my_function --runtime=python2.7
Looks like you didn't specified role arn for the function.
Do you want me to create default IAM role and configure it (my_function-lamma-role)? (y/n) y
I, [2017-04-06T22:28:53.952278 #5027]  INFO -- : Creating role my_function-lamma-role
I, [2017-04-06T22:28:54.863304 #5027]  INFO -- : Checking attached role policies for my_function-lamma-role
I, [2017-04-06T22:28:55.243400 #5027]  INFO -- : Could not find AWSLambdaBasicExecutionRole policy. Attatching.
I, [2017-04-06T22:28:55.243449 #5027]  INFO -- : Attaching minimal policy (AWSLambdaBasicExecutionRole) to my_function-lamma-role
I, [2017-04-06T22:28:55.466092 #5027]  INFO -- : Done
      create  /workspace/my_function/lambda_function.py
      create  /workspace/my_function/lamma.yml
I, [2017-04-06T22:28:55.468021 #5027]  INFO -- : Initializing git repo in /workspace/my_function
$ ls
total 16
drwxr-xr-x   5 yuichiro-someya  staff  170  4  6 22:28 ./
drwxr-xr-x   3 yuichiro-someya  staff  102  4  6 22:28 ../
drwxr-xr-x  10 yuichiro-someya  staff  340  4  6 22:28 .git/
-rw-r--r--   1 yuichiro-someya  staff  357  4  6 22:28 lambda_function.py
-rw-r--r--   1 yuichiro-someya  staff  207  4  6 22:28 lamma.yml
$ lamma deploy -a production
Function my_function doesn't seem to be exist on remote.
Do you want me to create it? (y/n) y
I, [2017-04-06T22:29:14.113561 #9209]  INFO -- : Creating new function my_function...
I, [2017-04-06T22:29:14.123244 #9209]  INFO -- : Saved the build: /var/folders/kd/_chs6sw13jvg01hlq6nd50k00000gp/T/lamma/05f92074cf833092343cd5414754efef
I, [2017-04-06T22:29:14.448724 #9209]  INFO -- : Created new function arn:aws:lambda:ap-northeast-1::function:my_function
I, [2017-04-06T22:29:14.448780 #9209]  INFO -- : Publishing...
I, [2017-04-06T22:29:14.605540 #9209]  INFO -- : Published $LATEST version as version 1 of funtion: arn:aws:lambda:ap-northeast-1::function:my_function:1
Function alias PRODUCTION doesn't seem to be exist on remote.
Do you want me to create it? (y/n) y

とまあこんな感じに、
shell-session
gem install lamma

して、

lamma init my_function --runtime=python2.7

して、 lambda_function.py 編集して

lamma deploy -a production

とすると、my_functionとPRODUCTIONエイリアスがクラウド上に生成されます。便利。

一方で、init時に生成されたlamma.ymlは次のように設定値がまるっとはいってます。

$ cat lamma.yml
function:
  name: my_function
  role_arn: arn:aws:iam:::role/my_function-lamma-role
  description: Hello, world.
  timeout: 3
  memory_size: 128
  runtime: python2.7
  region: ap-northeast-1

また、複数回デプロイした後は、

lamma rollback -a production

でエイリアスを指定して(デプロイごとに1回のみ)ロールバックすることが出来ます。
(deploy時にエイリアス毎に古いバージョンに向けたエイリアス(LAST)を用意することで実現している。)

lammaの紹介は以上。issueを上げてもらえると開発頑張れます。

続きを読む

Fluentdの正規表現を攻略する

formatのデバッグツール

Fluentdのログの正規表現が正しいかはFluentularで確認しならが操作できる。
http://fluentular.herokuapp.com/

スクリーンショット 2017-03-27 3.18.16.png

ただし、これはHerokuの無料プランで動いているらしく使用できないことも多いのでDockerからでもできる

$ docker pull tomohiro/fluentular
$ docker run -p 8080:8080 tomohiro/fluentular

作成方法

定義されたフォーマットを指定するか正規表現を利用します。

定義されたフォーマット一覧

fluentd側で予め10個の定義が用意されています。デフォルトのログの設定を利用している場合はこれらを使用することができると思われるので使用すると良いでしょう。

  • apache2
  • apache_error
  • nginx
  • syslog
  • tsv
  • csv
  • ltsv
  • json
  • none
  • multiline

ただし、注意点としてこれは常に通用するものではなく、該当する場合にのみ使用することができます。

apache2

format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

apache_error

format /^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])? \[client (?<client>[^\]]*)\] (?<message>.*)$/

nginx

format /^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

syslog

format /^(?<time>[^ ]*\s*[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/
time_format %b %d %H:%M:%S

tsv

format tsv
keys key1,key2,key3
time_key key2

csv

format csv
keys key1,key2,key3
time_key key3

ltsv

format ltsv
delimiter =               # Optional. ':' is used by default
time_key time_field_name

json

format json
time_key key3

none

format none
message_key my_message

multiline

format multiline
format_firstline /^Started/
format1 /Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/
format2 /Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/
format3 /(  Parameters: (?<parameters>[^ ]+)\n)?/
format4 /  Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/
format5 /Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/

正規表現で記述する

名前付きキャプチャ

正規表現から該当箇所と通り出したいときに()で括ったところは$1,$2,$3,…のように取り出しますが、名前付きキャプチャを利用することで取り出しの部分に名前をつけることができます。

(?<name>pattern)

http://qiita.com/jnchito/items/cceb669cb06fc044f411

これだけは抑えておきたい正規表現

^ log $

^以降にログのパターンを書き始め、$で終端。

[pattern]

patternの文字列

[^pattern]

pattarn以外の文字列

\S

空白文字(半角スペース、\t、\n、\r、\f)以外すべて

.

任意の1文字

*

0文字以上

+

1文字以上

\special

エスケープ文字の役割を担う文字を文字そのものとして表現したいときは\を文字の前に付ける
[,],”, 空白

(?: pattern )

パターンのグループを表す。

( pattern1 | pattern2 )

pattern1またはpattern2

実際に正規表現を組み立てる

Apacheのhttpd.confにおけるログの設定は以下の通りとなります。

ErrorLog "logs/error_log"
LogLevel warn

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %q" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b %q" common

    <IfModule logio_module>
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O %q" combinedio
    </IfModule>
    CustomLog "logs/access_log" combined
</IfModule>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

LogFormatの各項目の詳細は以下のURLを参照する。
https://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html

実際に吐き出されたアクセスログは以下の通りとなります。

10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"
10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1
10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"

3つめはELBによるヘルスチェックが記録されています。4つめはリクエストがタイムアウトされた様子を表しています。

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

上記のようなApacheのアクセスログを正規表現します。

正規表現 作成手順

1.最初に1つめから考えていきます。
最初は10.0.0.85から始まり、IPアドレスを表します。厳密に考えるとプライベートアドレスなどの範囲を考えてXXX.XXX.XXX.XXXの表記となるような表現となりますが、ログの吐き出しは吐き出しが限定されるので厳密にやる必要はなく、空白以外の文字が0文字以上となる表現でOK。空白以外は[^ ]で表されこれが0文字以上になるので[~ ]*で表される。これに名前付けをするために(?<host>[^ ]*)となる。最後にこのホスト名から始まるので^を先頭につけて、^(?<host>[^ ]*)となる。

2.次に空白を挟み、-が2回繰り返される。最初のremotelogが存在しないことによるもの。次の-はuserが存在しないことによるもの。(?<remotelog>[^ ]*) (?<user>[^ ]*)で表されます。空白に注意。以降空白の説明は省略します。

3.[03/Feb/2017:11:53:21 +0900]は時間を表します。時間については別途time_formatで指定します。[, ]ですが、これは特殊文字なので\でエスケープして使うのでこれは\[ time \]で表現します。次にtimeに相当するものですが、時間中には]は含まれないのでこれ以外の任意の文字列なので]は除外するためにこれ以外の文字が0文字以上となるので[^\]*を組み合わせて[^\]*で表現します。これに名前付けをするために(?<time>[^\]]*)となり、全て合わせて\[(?<time>[^\]]*)\]となります。
time_formatですが03/Feb/2017:11:53:21 +0900なのでそれぞれの日付表記に対応するフォーマットを使用して%d/%b/%Y:%H:%M:%S %zとなります。

4."GET /requests/form/34 HTTP/1.1"ですが、これは"で囲まれた要素に対して、GET/requests/form/34HTTP/1.1の要素から成ります。最初に"で囲まれているので"pattern"のようにします。
次にpatternについてですが、
GET /requests/form/34 HTTP/1.1ですが、GET(?<method>\S+)、このあとに空白が来て続いてくるので+/requests/form/34(?<path>[^ ]*)、空白が来るので、+HTTP/1.1が来るので\S*とします。

以下、同様にして組み立てていくと例えば、以下のような正規表現を作成することができます。

^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$

作成したものとFluentularの出力の見本は以下のURLとなります。
http://fluentular.herokuapp.com/parse?regexp=%5E%28%3F%3Chost%3E%5B%5E+%5D*%29+%28%3F%3Cremotelog%3E%5B%5E+%5D*%29+%28%3F%3Cuser%3E%5B%5E+%5D*%29+%5C%5B%28%3F%3Ctime%3E%5B%5E%5C%5D%5D*%29%5C%5D+%22%28%3F%3Cmethod%3E%5CS%2B%29%28%3F%3A+%2B%28%3F%3Cpath%3E%5B%5E+%5D*%29+%2B%5CS*%29%3F%22+%28%3F%3Cstatus%3E%5B%5E+%5D*%29+%28%3F%3Csize%3E%5B%5E+%5D*%29+%22%28%3F%3Creferer%3E%5B%5E%5C%22%5D*%29%22+%22%28%3F%3Cagent%3E.*%29%22%5C+*%28%3F%3Cquerystring%3E%5B%5E%5C%22%5D*%29%24&input=10.0.0.85+-+-+%5B03%2FFeb%2F2017%3A11%3A53%3A21+%2B0900%5D+%22GET+%2Frequests%2Fform%2F34+HTTP%2F1.1%22+200+95409+%22https%3A%2F%2Fsample.jp%2Fsample%3Fstation%3D%25E4%25B8%25AD%25E9%2587%258E%25E6%2596%25B0%25E6%25A9%258B%22+%22Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+10_2_1+like+Mac+OS+X%29+AppleWebKit%2F602.4.6+%28KHTML%2C+like+Gecko%29+Mobile%2F14D27%22&time_format=%25d%2F%25b%2F%25Y%3A%25H%3A%25M%3A%25S+%25z

正しく作成されている場合、以下のように名前付きラベルに対応する値の内容が表示される。

スクリーンショット 2017-03-27 3.17.58.png

サンプル

アクセスログ

ログ

10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"


10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"

正規表現

^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$
%d/%b/%Y:%H:%M:%S %z

エラーログ

ログ

[Sun Feb 19 03:08:02.063361 2017] [auth_digest:notice] [pid 26649] AH01757: generating secret for digest authentication ...
[Sun Feb 19 03:08:02.065290 2017] [lbmethod_heartbeat:notice] [pid 26649] AH02282: No slotmem from mod_heartmonitor
[Sun Feb 19 03:08:02.203091 2017] [mpm_prefork:notice] [pid 26649] AH00163: Apache/2.4.23 (Amazon) PHP/5.6.22 configured -- resuming normal operations
[Sun Feb 19 03:08:02.203473 2017] [core:notice] [pid 26649] AH00094: Command line: '/usr/sbin/httpd'
[Mon Feb 20 23:34:38.774239 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Mon Feb 20 23:34:39.373401 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Wed Feb 22 10:12:12.180706 2017] [:error] [pid 17757] [client 10.0.1.172:10406] CSRF state token does not match one provided.
[Wed Feb 22 16:45:54.469282 2017] [:error] [pid 27395] [client 10.0.0.85:59532] CSRF state token does not match one provided.
[Wed Feb 22 16:48:53.326150 2017] [:error] [pid 27433] [client 10.0.0.85:59628] PHP Fatal error:  Call to a member function is_in_area() on null in /var/www/sample/releases/20170222074811/fuel/app/classes/controller/api/requests.php on line 116, referer: https://sample.jp/requests/confirm?judge=2
~

正規表現

^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$
%a %b %d %H:%M:%S.%N %Y

td-agent.confの設定例

td-agent.conf
<source>
  type tail
  format /^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$/
  time_format %d/%b/%Y:%H:%M:%S %z
  path /var/log/httpd/access_log
  tag sample.service.access.log.aggregate
  pos_file /tmp/fluent/logpos/access_log.pos
</source>

<source>
  type tail
  format /^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$/
  time_format %a %b %d %H:%M:%S.%N %Y
  path /var/log/httpd/error_log
  tag sample.service.error.log.aggregate
  pos_file /tmp/fluent/logpos/error_log.pos
</source>

<match **>
  type forward

  <server>
    name fluent01
    host 10.0.0.31
    port 24224
  </server>
</match>
td-agent.conf
<source>
  type forward
  port 24224
</source>

<match sample.service.access.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name access_log
    logstash_format true
    logstash_prefix sample-service-log-access
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path access-logs/
    buffer_path /tmp/fluent/s3/access/
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

<match sample.service.error.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name error_log
    logstash_format true
    logstash_prefix sample-service-log-error
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path error-logs/
    buffer_path /tmp/fluent/s3/error
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

(参照)権限設定

$ sudo chgrp td-agent /var/log/httpd/
$ sudo chgrp td-agent /var/log/messages

$ sudo chmod g+rx /var/log/httpd/
$ sudo chmod g+rx /var/log/messages

以上

Appendix

td-agent.conf パラメータ一覧

type
tag
path
exclude_path
refresh_interval
limit_recently_modified
skip_refresh_on_startup
read_from_head
read_lines_limit
multiline_flush_interval
pos_file
format
time_format
path_key
rotate_wait
enable_watch_timer

参考

http://docs.fluentd.org/v0.12/articles/in_tail
http://www.tachibanakikaku.com/2013/12/fluentdformat.html

続きを読む

爆速API構築〜chalice編〜

爆速でAPIをリリースしたい方へ送ります

https://github.com/awslabs/chalice

chaliceとは

聖杯の名を冠するこのchaliceはフレームワークというよりはツールで、アプリケーションコードさえあれば、AWS Lambda、AWS APIGateway、IAMRoleなどの設定をよしなにやって、AWS上にデプロイをキメてくれるという活かしたシロモノです。これにより爆速でAPIを構築して、プロダクションリリースを実現します。

まずはじめに

兎にも角にもAWSのサービスをフルフル使ってAPI構築していきますので、credentialの設定をまずはじめに行っておく必要があります。

$ mkdir ~/.aws
$ cat >> ~/.aws/config
[default]
aws_access_key_id=YOUR_ACCESS_KEY_HERE
aws_secret_access_key=YOUR_SECRET_ACCESS_KEY
region=ap-northeast-1

複数のAWSアカウントを切り替えてお使いの方は、direnvなどで環境変数にてACCESSKEYを設定しておくことをオススメいたします。SDKによりますが、今回使うboto3に関してはAWSCliとコンパチな感じの優先度でcredentialや環境変数の読んでいるっぽいので、ここらへんの記事を参照されると幸せになれるかもしれません。

プロジェクト作成からのデプロイキメる方法

下記のようにコマンドラインにぶち込んでみましょう

$ pip install chalice
$ chalice new-project helloworld && cd helloworld
Initial creation of lambda function.
Creating role
Creating deployment package.
Lambda deploy done.
Initiating first time deployment...
Deploying to: dev
https://endpoint/dev/

$ curl https://endpoint/dev
{"hello": "world"}

上記の一連の作業は、プロジェクトを新規作成後、すぐデプロイをしています。
chalice deploy という魔法の一言で、プロジェクトを AWS Lambda にデプロイして、 AWS API GatewayAWS Lambda の紐付けをよしなにやってパブリックにアクセス可能な状態にデプロイしてくれるのです。

APIの内容をごちゃごちゃしたい場合は、 新規作成時に作成される app.py の中身を本能のままにいじっていきましょう

S3にログを吐き出すAPIを作る方法

自分の周りだととりあえずログを溜めておくAPIが欲しい、みたいな要件が多いです
とりあえずS3にJSONで保存しておいて後でサルベージするなりなんなり、というケースが多々あります
特に今はAWS AthenaでJSONの集計が対話的にできたりみたいな事があったりしてことさらその要件でのお話を頂くことが多いです

ではAPIにJSONをPOSTするとその形式でJSONをS3に書いてくれるようなAPIを作成してみましょう

まずS3上にbucketを作っておきましょう(これは手動で作業が必要です)
そしてAWSSDKをinstallしましょう

$ pip install boto3

app.py を下記のように書き換えてみましょう

app.py
from chalice import Chalice

app = Chalice(app_name='helloworld')
import json
import boto3
from botocore.exceptions import ClientError

from chalice import NotFoundError

S3 = boto3.client('s3', region_name='ap-northeast-1')
BUCKET = 'your-bucket-name'

@app.route('/objects/{key}', methods=['GET', 'PUT'])
def s3objects(key):
    request = app.current_request
    if request.method == 'PUT':
        S3.put_object(Bucket=BUCKET, Key=key,
                      Body=json.dumps(request.json_body))
    elif request.method == 'GET':
        try:
            response = S3.get_object(Bucket=BUCKET, Key=key)
            return json.loads(response['Body'].read())
        except ClientError as e:
            raise NotFoundError(key)

書き換えたらデプロイしてみましょう

$ chalice deploy
Updating IAM policy.

The following actions will be added to the execution policy:

s3:GetObject
s3:PutObject

Would you like to continue?  [Y/n]: Y
Updating lambda function...
Regen deployment package...
Sending changes to lambda.
Lambda deploy done.
API Gateway rest API already found.
Deleting root resource id
Done deleting existing resources.
Deploying to: dev
https://endpoint/dev/

サラッとYを入力してデプロイしてしまいましたが、実はここでIAMRoleの設定を chalice がよしなにやってくれています
詳しく説明すると、デプロイ時にデプロイ対象のソースコードをパースして、必要な権限を割り出して、その権限、つまり下記の二つの権限を設定しても良いか聞いてくれています

  • s3:GetObject
  • s3:PutObject

そしてその権限をIAMに設定して、LambdaからS3へアクセスできるような状態でデプロイをキメてくれます
つまりアプリケーション構築する人間はIAMの設定を意識することなく、IAMの設定がなされた上でデプロイされます

なんて恐ろしい素晴らしい気の利かせ方でしょうか!!
IAMの設定はすごいめんどくさいですし、えーいAdminつけとけみたいな話にもなりかねないので、自動で適切に設定してくれるとなると嬉しい限りです

補足〜健康的なPythonでの開発のために〜

Pythonは2.X系と3.X系の乖離があり世界に深い悲しみがあります
AWS Lambda上のPythonは2.7系なので3.X系と違います
そのため、手元の環境を合わせた方が良い、というケースが多々生じます

そんな深い悲しみから決別するために手元の開発環境を任意の環境に切り替えるために
pyenvを入れておきましょう

続きを読む

Salesforce Apex で独自の Salesforce REST API を作成する (取引先責任者をLIKE検索して表示)

はじめに

本資料では、独自のSalesforce REST APIを作成する手順について記しています。

 (1) Salesforce Apexクラスを作成し、独自のSalesforce REST APIを作成する手順
 (2) PHPから作成したSalesforce REST APIを実行するサンプルコード

参考資料

ApexによるSalesforce REST API作成については、以下の資料を参考にさせて頂きました。ありがとうございました。

http://kayakuguri.github.io/blog/2014/09/08/apex-restapi/
https://developer.salesforce.com/page/Creating_REST_APIs_using_Apex_REST

Apexで独自に作成したSalesforce REST APIについては、以下の手順により構築したAmazon EC2インスタンス(Amazon Linux)上で実行しています。

http://qiita.com/na0AaooQ/items/157c28a80948c4b97bed

Apexクラスで独自のSalesforce REST APIを作成する

(1) https://login.salesforce.com/ へログインします。

(2)「設定」->「ビルド」->「開発」->「Apexクラス」->「新規」をクリックします。

スクリーンショット 2017-03-14 1.54.49.png

(3)「Apex Class」画面が表示されます。Apexクラスを作成します。

「Apex Class Edit」テキストエリアに以下のApexコードを貼り付けます。

これは、取引先責任者(Contact)のName項目について、指定した文字列が含まれているレコードを返すApexクラスです。

例えば、今回作成するSalesforce REST APIに対して ‘テスト’ というパラメータをGETで指定した場合、取引先責任者(Contact)のName項目をLIKE検索して ‘テスト’ という文字列が含まれているレコードを返します。

@RestResource(urlMapping='/CustomContact/*')
global with sharing class CustomContactSample {
    @HttpGet
    global static sObject doGet() {
        RestRequest req = RestContext.request;
        String ContactName = req.params.get('name');
        ContactName = '%' + ContactName + '%';
        try {
            Contact acc = [SELECT Id, Name, Email, Account.Name FROM Contact WHERE Name LIKE :ContactName LIMIT 1];
            return acc;
        } catch (exception e) {
            return null;
        }
    }
}

「Save」をクリックして、Apexクラスを作成します。

スクリーンショット 2017-03-14 1.58.01.png

(4) 「設定」->「ビルド」->「開発」->「Apexクラス」をクリックします。Apexクラス「CustomContactSample」が作成されている事を確認します。

スクリーンショット 2017-03-14 2.01.36.png

「CustomContactSample」をクリックします。先ほどApexクラス作成時に貼り付けたコードが表示される事を確認します。

スクリーンショット 2017-03-14 2.03.31.png

(5)「設定」->「管理」->「ユーザの管理」->「プロファイル」をクリックします。

スクリーンショット 2017-03-14 2.10.38.png

(6)「テストオブジェクトプロファイル」をクリックします。

(7) プロファイル画面が表示されます。「有効な Apex クラス」をクリックします。

スクリーンショット 2017-03-14 2.12.10.png

(8)「有効な Apex クラス」の「編集」をクリックします。

スクリーンショット 2017-03-14 2.13.09.png

(9)「Apex クラスアクセスを有効化」画面が表示されます。「利用可能な Apex クラス」で「CustomContactSample」を選択して「追加」をクリックします。

「有効化された Apex クラス」に「CustomContactSample」が追加された事を確認します。

スクリーンショット 2017-03-14 2.15.33.png

(10) .bashrcにSalesforce REST APIのクレデンシャルを追加します。

以下を参考にして、.bashrcにSalesforce REST APIクレデンシャルを追加します。

http://qiita.com/na0AaooQ/items/157c28a80948c4b97bed

.bashrcにSalesforce REST APIクレデンシャルを追加します。

[ec2-user@salesforce-api-test ~]$ vi /home/ec2-user/.bashrc
 (末尾に以下を追加する)

### Sandbox環境へ接続する場合のAPIエンドポイント
##export DATABASEDOTCOM_HOST="test.salesforce.com"
### 本番環境へ接続する場合のAPIエンドポイント
export DATABASEDOTCOM_HOST="login.salesforce.com"

export DATABASEDOTCOM_CLIENT_ID="前述の「コンシューマ鍵」(OAuthコンシューマキー)を記載します。"
export DATABASEDOTCOM_CLIENT_SECRET="前述の「コンシューマの秘密」(OAuthコンシューマシークレット)を記載します。"
export DATABASEDOTCOM_CLIENT_USERNAME="APIを有効化しているSalesforceユーザアカウント名を記載します。"
export DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD="APIを有効化しているSalesforceユーザアカウントのパスワードを記載します。"

.bashrcに追加した環境変数を読み込みます。

[ec2-user@salesforce-api-test ~]$ source /home/ec2-user/.bashrc
[ec2-user@salesforce-api-test ~]$ 

作成した独自のSalesforce REST APIをPHPから実行する

(11) 独自に作ったSalesforce REST APIを呼び出すPHPサンプルプログラムを作成します。

[ec2-user@salesforce-api-test ~]$ php -v
PHP 5.3.29 (cli) (built: May 12 2015 22:42:19) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2014 Zend Technologies
[ec2-user@salesforce-api-test ~]$ 
[ec2-user@salesforce-api-test ~]$ vi /home/ec2-user/select_salesforce_custom_api_contact.php
/home/ec2-user/select_salesforce_custom_api_contact.php
<?php

require_once("/home/ec2-user/Force.com-OAuth-Toolkit-for-PHP/oauth.php");

if ( $argc == 2 ) {
  $name = "$argv[1]";
}
else {
  echo "[Usage]: $argv[0] テストn";
  exit;
}

///// Salesforce REST API接続用の環境変数を.bashrcから取得する
// Salesforce REST APIで接続するアプリケーションの「コンシューマ鍵」
$DATABASEDOTCOM_CLIENT_ID = getenv('DATABASEDOTCOM_CLIENT_ID');

// Salesforce REST APIで接続するアプリケーションの「コンシューマの秘密」を.bashrcから取得する
$DATABASEDOTCOM_CLIENT_SECRET = getenv('DATABASEDOTCOM_CLIENT_SECRET');

// Salesforce REST API接続用のSalesforceユーザ(Salesforceログインに使用しているEメールアドレスを設定)を.bashrcから取得する
$DATABASEDOTCOM_CLIENT_USERNAME = getenv('DATABASEDOTCOM_CLIENT_USERNAME');

// Salesforce REST API接続用のSalesforceユーザのパスワードを.bashrcから取得する
$DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD = getenv('DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD');

// Salesforce REST APIエンドポイントを.bashrcから取得する
$DATABASEDOTCOM_HOST = getenv('DATABASEDOTCOM_HOST');
$LOGIN_URL = "https://" . $DATABASEDOTCOM_HOST . "/";

///// Salesforce REST API接続用の設定
$CACHE_DIR = '/home/ec2-user/tmp/session';
$CALLBACK_URL = 'https://localhost/callback';

/////
// Salesforce REST API接続用のOauthインスタンスを生成
$oauth = new oauth( $DATABASEDOTCOM_CLIENT_ID, $DATABASEDOTCOM_CLIENT_SECRET, $CALLBACK_URL, $LOGIN_URL, $CACHE_DIR);

// Salesforce REST API接続にあたりSalesforceへの認証を実行
$oauth->auth_with_password( $DATABASEDOTCOM_CLIENT_USERNAME, $DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD );

// カスタムAPIのURLを指定する
$url = $oauth->instance_url . "/services/apexrest/CustomContact/?name=" . $name;
$curl = curl_init($url);
var_dump($url);

curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Authorization: OAuth " . $oauth->access_token));

// Salesforce REST API実行結果を保存
$response = json_decode(curl_exec($curl), true);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);

if ( $status != 200 ) {
    print("Salesforce Custom REST API Access Failed  StatusCode =[" . $status . "]n");
} else {
    print("Salesforce Custom REST API Access Success StatusCode =[" . $status . "]n");
}

var_dump( $response );

curl_close($curl);

$oauth->auth_with_refresh_token();

?>

(12) 作成したPHPサンプルプログラムの構文に問題ない事をチェックします。

[ec2-user@salesforce-api-test ~]$ php -l /home/ec2-user/select_salesforce_custom_api_contact.php 
No syntax errors detected in /home/ec2-user/select_salesforce_custom_api_contact.php
[ec2-user@salesforce-api-test ~]$ 

(13) PHPサンプルプログラムを実行し、独自に作ったSalesforce REST APIを呼び出します。

PHPサンプルプログラムを実行すると、独自に作ったSalesforce REST APIを呼び出します。

以下の例では、Salesforce REST APIに対して ‘テスト’ というパラメータをGETで渡して、取引先責任者(Contact)のName項目をLIKE検索して ‘テスト’ という文字列が含まれているレコードを表示しています。

[ec2-user@salesforce-api-test ~]$ php /home/ec2-user/select_salesforce_custom_api_contact.php テスト
string(74) "https://ap2.salesforce.com/services/apexrest/CustomContact/?name=テスト"
Salesforce Custom REST API Access Success StatusCode =[200]
array(6) {
  ["attributes"]=>
  array(2) {
    ["type"]=>
    string(7) "Contact"
    ["url"]=>
    string(56) "/services/data/v39.0/sobjects/Contact/00**************AS"
  }
  ["Id"]=>
  string(18) "00**************AS"
  ["Name"]=>
  string(16) "テスト 太郎"
  ["Email"]=>
  string(19) "example@example.com"
  ["AccountId"]=>
  string(18) "00**************AH"
  ["Account"]=>
  array(3) {
    ["attributes"]=>
    array(2) {
      ["type"]=>
      string(7) "Account"
      ["url"]=>
      string(56) "/services/data/v39.0/sobjects/Account/00**************AH"
    }
    ["Id"]=>
    string(18) "00**************AH"
    ["Name"]=>
    string(21) "テスト株式会社"
  }
}
[ec2-user@salesforce-api-test ~]$ 

今回のAPIでは、取引先責任者の「テスト 太郎」のレコードを表示しています。

スクリーンショット 2017-03-14 2.28.14.png


以上になります。

続きを読む

RubyからSalesforce REST APIを実行する (restforceを使用してSalesforceカスタムオブジェクトへレコードを追加する)

はじめに

Salesforceに対して、様々な操作を行えるSalesforce REST APIというものがあります。

Salesforce REST APIを使用する事で、RubyからSalesforceカスタムオブジェクトにレコードを追加したり、レコードを取得したりといった操作が可能です。

RubyからSalesforce REST APIを実行する事で、Salesforceカスタムオブジェクトにレコードを追加するサンプルプログラムを記します。

また、Salesforceカスタムオブジェクトに登録したレコード一覧を表示するサンプルプログラムも合わせて記します。

実行環境

RubyからSalesforce REST APIを呼び出す環境については、以下の手順を参考にして、AWS EC2インスタンス(Amazon Linux)に作成しました。
http://qiita.com/na0AaooQ/items/d7f814b184c6c1bd9d9c

レコード追加対象のSalesforceカスタムオブジェクト(カスタムオブジェクト名: QuickStart__c)については、以下の手順で作成したものを使用します。また、EC2インスタンスからSalesforce REST APIを呼び出す為のSalesforce OAuth設定も以下をご参照下さい。
http://qiita.com/na0AaooQ/items/157c28a80948c4b97bed

Salesforce REST API接続に使用したRubyのGem

今回の手順では「databasedotcom」ではなく「restforce」というGemを使用して、RubyからSalesforce REST APIを実行します。
https://github.com/ejholmes/restforce

Gem「databasedotcom」を使用したSalesforce REST APIの実行方法については、以下をご参照下さい。
http://qiita.com/na0AaooQ/items/1ac53c3899755f935c7b

EC2インスタンス(Amazon Linux)へのRubyインストール

(1) EC2インスタンス(Amazon Linux)にRubyをインストールします。

以下の手順でEC2インスタンス(Amazon Linux)にRubyをインストールします。

http://qiita.com/na0AaooQ/items/d7f814b184c6c1bd9d9c

EC2インスタンス(Amazon Linux)へのrestforce(Gem)インストール

(2) .bashrcにSalesforce REST API接続用の設定を追加します。

[ec2-user@example-ruby-sinatra-server ~]$ cp -p /home/ec2-user/.bashrc /home/ec2-user/.bashrc.ORG
[ec2-user@example-ruby-sinatra-server ~]$ diff /home/ec2-user/.bashrc /home/ec2-user/.bashrc.ORG
[ec2-user@example-ruby-sinatra-server ~]$ 
[ec2-user@example-ruby-sinatra-server ~]$ vi /home/ec2-user/.bashrc
 (末尾に以下を追加する)

### Sandbox環境へ接続する場合のAPIエンドポイント
##export DATABASEDOTCOM_HOST="test.salesforce.com"
### 本番環境へ接続する場合のAPIエンドポイント
export DATABASEDOTCOM_HOST="login.salesforce.com"

export DATABASEDOTCOM_CLIENT_ID="前述の「コンシューマ鍵」(OAuthコンシューマキー)を記載します。"
export DATABASEDOTCOM_CLIENT_SECRET="前述の「コンシューマの秘密」(OAuthコンシューマシークレット)を記載します。"
export DATABASEDOTCOM_CLIENT_USERNAME="salesforce_api_testuser@**********.salesforce.example.com"
export DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD="APIを有効化しているSalesforceユーザアカウントのパスワードを記載します。"

(3) .bashrcに追加した環境変数を読み込みます。

[ec2-user@example-ruby-sinatra-server ~]$ source /home/ec2-user/.bashrc
[ec2-user@example-ruby-sinatra-server ~]$ 

(4) EC2インスタンス(Amazon Linux)に/home/ec2-user/select_salesforce_restforce_api_testディレクトリを作成し、restforce(Gem)をインストールします。

今回の例では、レコード追加とレコード表示プログラムを各ディレクトリに作成していく事にします。

プログラム内容 作成するディレクトリ名
カスタムオブジェクトにレコードを追加するプログラム /home/ec2-user/insert_salesforce_restforce_api_test
カスタムオブジェクトのレコードを表示するプログラム /home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server ~]$ hostname
example-ruby-sinatra-server
[ec2-user@example-ruby-sinatra-server ~]$ id
uid=500(ec2-user) gid=500(ec2-user) groups=500(ec2-user),10(wheel)
[ec2-user@example-ruby-sinatra-server ~]$ mkdir /home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server ~]$ cd /home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/select_salesforce_restforce_api_test/
合計 8
drwx------ 8 ec2-user ec2-user 4096  3月 11 13:02 ..
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:02 .
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

Gemfileを作成します。

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ pwd
/home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ bundle init
Writing new Gemfile to /home/ec2-user/select_salesforce_restforce_api_test/Gemfile
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/select_salesforce_restforce_api_test/
合計 12
drwx------ 8 ec2-user ec2-user 4096  3月 11 13:02 ..
-rw-r--r-- 1 ec2-user ec2-user   75  3月 11 13:03 Gemfile
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:03 .
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

念の為、作業開始前のGemfileをバックアップしておきます。

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ cp -p /home/ec2-user/select_salesforce_restforce_api_test/Gemfile /home/ec2-user/select_salesforce_restforce_api_test/Gemfile.ORG
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ diff /home/ec2-user/select_salesforce_restforce_api_test/Gemfile /home/ec2-user/select_salesforce_restforce_api_test/Gemfile.ORG
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ cat /home/ec2-user/select_salesforce_restforce_api_test/Gemfile
# frozen_string_literal: true
source "https://rubygems.org"

# gem "rails"
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

Gemfileにrestforce ( https://github.com/ejholmes/restforce )を追記します。

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ echo 'gem "restforce"' >> /home/ec2-user/select_salesforce_restforce_api_test/Gemfile
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ cat /home/ec2-user/select_salesforce_restforce_api_test/Gemfile
# frozen_string_literal: true
source "https://rubygems.org"

# gem "rails"
gem "restforce"
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ diff /home/ec2-user/select_salesforce_restforce_api_test/Gemfile /home/ec2-user/select_salesforce_restforce_api_test/Gemfile.ORG
5d4
< gem "restforce"
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

restforce(Gem)がまだインストールされていない事を確認します。

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ pwd
/home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ bundle list
Could not find gem 'restforce' in any of the gem sources listed in your Gemfile or available on this machine.
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

restforce(Gem)をインストールします。

2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ pwd
/home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/select_salesforce_restforce_api_test/
合計 16
drwx------ 8 ec2-user ec2-user 4096  3月 11 13:02 ..
-rw-r--r-- 1 ec2-user ec2-user   75  3月 11 13:03 Gemfile.ORG
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:06 .
-rw-r--r-- 1 ec2-user ec2-user   91  3月 11 13:06 Gemfile
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/.........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...
Installing multipart-post 2.0.0
Installing hashie 3.5.5
Installing json 2.0.3 with native extensions
Using bundler 1.13.2
Installing faraday 0.9.2
Installing faraday_middleware 0.11.0.1
Installing restforce 2.5.0
Bundle complete! 1 Gemfile dependency, 7 gems now installed.
Bundled gems are installed into ./vendor/bundle.
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

restforce(Gem)がインストールされた事を確認します。

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ pwd
/home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ bundle list | grep restforce
  * restforce (2.5.0)
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ bundle list
Gems included by the bundle:
  * bundler (1.13.2)
  * faraday (0.9.2)
  * faraday_middleware (0.11.0.1)
  * hashie (3.5.5)
  * json (2.0.3)
  * multipart-post (2.0.0)
  * restforce (2.5.0)
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/select_salesforce_restforce_api_test/
合計 28
drwx------ 8 ec2-user ec2-user 4096  3月 11 13:02 ..
-rw-r--r-- 1 ec2-user ec2-user   75  3月 11 13:03 Gemfile.ORG
-rw-r--r-- 1 ec2-user ec2-user   91  3月 11 13:06 Gemfile
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:08 .bundle
drwxrwxr-x 3 ec2-user ec2-user 4096  3月 11 13:08 vendor
-rw-rw-r-- 1 ec2-user ec2-user  433  3月 11 13:08 Gemfile.lock
drwxrwxr-x 4 ec2-user ec2-user 4096  3月 11 13:08 .
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

(5) EC2インスタンス(Amazon Linux)に/home/ec2-user/insert_salesforce_restforce_api_testディレクトリを作成し、restforce(Gem)をインストールします。

[ec2-user@example-ruby-sinatra-server ~]$ hostname
example-ruby-sinatra-server
[ec2-user@example-ruby-sinatra-server ~]$ mkdir /home/ec2-user/insert_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server ~]$ cd /home/ec2-user/insert_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ pwd
/home/ec2-user/insert_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/insert_salesforce_restforce_api_test
合計 8
drwx------ 9 ec2-user ec2-user 4096  3月 11 13:13 ..
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:13 .
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 

前述の手順(4)を参考にして、restforce(Gem)をインストールします。

[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/insert_salesforce_restforce_api_test
合計 28
drwx------ 9 ec2-user ec2-user 4096  3月 11 13:13 ..
-rw-r--r-- 1 ec2-user ec2-user   75  3月 11 13:14 Gemfile.ORG
-rw-r--r-- 1 ec2-user ec2-user   91  3月 11 13:15 Gemfile
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:15 .bundle
drwxrwxr-x 3 ec2-user ec2-user 4096  3月 11 13:15 vendor
-rw-rw-r-- 1 ec2-user ec2-user  433  3月 11 13:15 Gemfile.lock
drwxrwxr-x 4 ec2-user ec2-user 4096  3月 11 13:15 .
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ bundle list | grep restforce
  * restforce (2.5.0)
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ bundle list
Gems included by the bundle:
  * bundler (1.13.2)
  * faraday (0.9.2)
  * faraday_middleware (0.11.0.1)
  * hashie (3.5.5)
  * json (2.0.3)
  * multipart-post (2.0.0)
  * restforce (2.5.0)
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 

RubyからSalesforce REST APIを実行してSalesforceカスタムオブジェクトのレコードを表示する

Rubyからrestforce(Gem)経由でSalesforce REST APIを呼び出し、Salesforceカスタムオブジェクトのレコードを表示します。

Salesforceカスタムオブジェクト「テストオブジェクト」(例としてオブジェクト名が QuickStart__c であるとします)のレコードを表示するRubyプログラムを作成します。

SOQL(Salesforce Object Query Language)のWHERE句により、以下の条件に一致するレコードのみ表示しています。

 [WHERE句の条件]
  本日登録したレコード ( create_date__c >= TODAY )

SOQLの日付形式と日付リテラルの詳細については Force.com SOQL および SOSL リファレンス をご参照下さい。

これを実行すると、Salesforceカスタムオブジェクトに登録されているレコードを表示出来ます。

[ec2-user@example-ruby-sinatra-server ~]$ cd /home/ec2-user/select_salesforce_restforce_api_test/
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ pwd
/home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ ls -lrta /home/ec2-user/select_salesforce_restforce_api_test/
合計 32
-rw-r--r-- 1 ec2-user ec2-user   75  3月 11 13:03 Gemfile.ORG
-rw-r--r-- 1 ec2-user ec2-user   91  3月 11 13:06 Gemfile
drwxrwxr-x 2 ec2-user ec2-user 4096  3月 11 13:08 .bundle
drwxrwxr-x 3 ec2-user ec2-user 4096  3月 11 13:08 vendor
-rw-rw-r-- 1 ec2-user ec2-user  433  3月 11 13:08 Gemfile.lock
drwx------ 9 ec2-user ec2-user 4096  3月 11 13:24 ..
drwxrwxr-x 4 ec2-user ec2-user 4096  3月 11 13:24 .
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ vi /home/ec2-user/select_salesforce_restforce_api_test/select_salesforce_api_test.rb
/home/ec2-user/select_salesforce_restforce_api_test/select_salesforce_api_test.rb
#!/bin/env ruby
#
require 'bundler/setup'
require 'restforce'

# Salesforce接続用インスタンス作成とSalesforce API認証
client = Restforce.new(
     host: ENV['DATABASEDOTCOM_HOST'],
     username: ENV['DATABASEDOTCOM_CLIENT_USERNAME'],
     password: ENV['DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD'],
     client_id: ENV['DATABASEDOTCOM_CLIENT_ID'],
     client_secret: ENV['DATABASEDOTCOM_CLIENT_SECRET']
)

# Salesforceカスタムオブジェクトに登録されているレコード一覧を表示
client.query("SELECT user_name__c, user_email__c, create_date__c FROM QuickStart__c WHERE create_date__c >= TODAY").each do|record|
  p "#{record.user_name__c} : #{record.user_email__c} #{record.create_date__c}"
end

Rubyプログラムを実行すると、Salesforceカスタムオブジェクトに登録されているレコードを表示出来ます。

[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ pwd
/home/ec2-user/select_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ ruby /home/ec2-user/select_salesforce_restforce_api_test/select_salesforce_api_test.rb
"testname_Ruby_20170311_133426 : testname_Ruby_20170311_133426@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_134741 : testname_Ruby_20170311_134741@hoge.example.com 2017-03-11"
"testname_Node_20170311_114548 : testname_Node_20170311_114548@hoge.example.com 2017-03-11"
"testname_Node_20170311_122544 : testname_Node_20170311_122544@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_114515 : testname_Ruby_20170311_114515@hoge.example.com 2017-03-11"
"testname_Node_20170311_122603 : testname_Node_20170311_122603@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_114654 : testname_Ruby_20170311_114654@hoge.example.com 2017-03-11"
"testname_Node_20170311_121201 : testname_Node_20170311_121201@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_134744 : testname_Ruby_20170311_134744@hoge.example.com 2017-03-11"
"testname_Node_20170311_123050 : testname_Node_20170311_123050@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_114637 : testname_Ruby_20170311_114637@hoge.example.com 2017-03-11"
"testname_Ruby120170311_134632 : testname_Ruby120170311_134632@hoge.example.com 2017-03-11"
"testname_Ruby1_20170311_134643 : testname_Ruby1_20170311_134643@hoge.example.com 2017-03-11"
"testname_Node_20170311_114546 : testname_Node_20170311_114546@hoge.example.com 2017-03-11"
"testname_Node_20170311_122616 : testname_Node_20170311_122616@hoge.example.com 2017-03-11"
[ec2-user@example-ruby-sinatra-server select_salesforce_restforce_api_test]$ 

RubyからSalesforce REST APIを実行してSalesforceカスタムオブジェクトにレコードを追加する

Rubyからrestforce(Gem)経由でSalesforce REST APIを呼び出し、Salesforceカスタムオブジェクトにレコードを追加します。

Salesforceカスタムオブジェクトへのレコード追加後、合わせてSOQL(Salesforce Object Query Language)のWHERE句により、以下の条件にANDで一致するレコードのみ表示しています。

 [WHERE句の条件1]
  Salesforceカスタムオブジェクトのuser_email_c項目に’Ruby’という文字列が含まれる ( useremail__c LIKE ‘%Ruby%’ )

   AND
 [WHERE句の条件2]
  本日登録したレコード ( create_date__c >= TODAY )

Salesforceカスタムオブジェクト「テストオブジェクト」(例としてオブジェクト名が QuickStart__c であるとします)にレコードを追加するRubyプログラムを作成します。

[ec2-user@example-ruby-sinatra-server ~]$ cd /home/ec2-user/insert_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 

[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ vi /home/ec2-user/insert_salesforce_restforce_api_test/insert_salesforce_api_test.rb
/home/ec2-user/insert_salesforce_restforce_api_test/insert_salesforce_api_test.rb
#!/bin/env ruby
#
require 'bundler/setup'
require 'restforce'
require 'date'

# Salesforce接続用インスタンス作成とSalesforce API認証
client = Restforce.new(
     host: ENV['DATABASEDOTCOM_HOST'],
     username: ENV['DATABASEDOTCOM_CLIENT_USERNAME'],
     password: ENV['DATABASEDOTCOM_CLIENT_AUTHENTICATE_PASSWORD'],
     client_id: ENV['DATABASEDOTCOM_CLIENT_ID'],
     client_secret: ENV['DATABASEDOTCOM_CLIENT_SECRET']
)

now = Time.now
create_date = now.strftime("%Y-%m-%d")
insert_time = now.strftime("%Y%m%d_%H%M%S")
insert_name = "testname_Ruby_" + insert_time
insert_email = insert_name + "@hoge.example.com"

# Salesforceカスタムオブジェクトにレコードを登録する
client.create(
  'QuickStart__c',
  user_name__c: insert_name,
  user_email__c: insert_email,
  create_date__c: create_date
)

# Salesforceカスタムオブジェクトに登録されているレコード一覧を表示
client.query("SELECT user_name__c, user_email__c, create_date__c FROM QuickStart__c WHERE user_email__c LIKE '%Ruby%' AND create_date__c >= TODAY").each do|record|
  p "#{record.user_name__c} : #{record.user_email__c} #{record.create_date__c}"
end

これを実行すると、Salesforceカスタムオブジェクトへレコードを追加出来ます。

[ec2-user@example-ruby-sinatra-server ~]$ cd /home/ec2-user/insert_salesforce_restforce_api_test/
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ pwd
/home/ec2-user/insert_salesforce_restforce_api_test
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ ruby /home/ec2-user/insert_salesforce_restforce_api_test/insert_salesforce_api_test.rb
"testname_Ruby_20170311_133426 : testname_Ruby_20170311_133426@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_134741 : testname_Ruby_20170311_134741@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_114515 : testname_Ruby_20170311_114515@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_114654 : testname_Ruby_20170311_114654@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_134744 : testname_Ruby_20170311_134744@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_114637 : testname_Ruby_20170311_114637@hoge.example.com 2017-03-11"
"testname_Ruby120170311_134632 : testname_Ruby120170311_134632@hoge.example.com 2017-03-11"
"testname_Ruby1_20170311_134643 : testname_Ruby1_20170311_134643@hoge.example.com 2017-03-11"
"testname_Ruby_20170311_135450 : testname_Ruby_20170311_135450@hoge.example.com 2017-03-11"
[ec2-user@example-ruby-sinatra-server insert_salesforce_restforce_api_test]$ 

Salesforceカスタムオブジェクトに追加されたレコード

前述のRubyプログラムを実行する事で、Salesforceカスタムオブジェクト(QuickStart__c = テストオブジェクト)に以下のようにレコードが追加されました。

スクリーンショット 2017-03-11 13.57.52.png

スクリーンショット 2017-03-11 13.58.13.png

スクリーンショット 2017-03-11 13.58.20.png


以上になります。

続きを読む