awsでruby on railsとapacheとの連携構築方法

すべての手順

必要なミドルウェアのインストール

$ sudo yum update
$ sudo yum install curl-devel ncurses-devel gdbm-devel readline-devel sqlite-devel ruby-devel
$ sudo yum install gcc gcc-c++ openssl-devel zlib-devel make patch git gettext perl rpm-build libxml2

rbenvのインストール

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:${PATH}"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ env | grep RBENV
RBENV_SHELL=bash
$ rbenv --version
rbenv 1.1.0-2-g4f8925a

ruby2.4.0 のインストール

$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv install -l
.....
2.3.0-dev
  2.3.0-preview1
  2.3.0-preview2
  2.3.0
  2.3.1
  2.3.2
  2.3.3
  2.4.0-dev
  2.4.0-preview1
  2.4.0-preview2
  2.4.0-preview3
  2.4.0-rc1
  2.4.0
  2.5.0-dev
  jruby-1.5.6
  jruby-1.6.3
  jruby-1.6.4
  jruby-1.6.5
  jruby-1.6.5.1
...
$ rbenv install -v 2.4.0
$ rbenv rehash
$ rbenv global 2.4.0
$ ruby -v
 ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

Railsのインストール

$ gem update --system
$ gem install bundler --no-rdoc --no-ri
$ gem install --no-ri --no-rdoc rails
$ rbenv rehash    
$ rails -v
Rails 5.0.2
$ gem install bundler

sqlite3のインストール

$ wget https://www.sqlite.org/2017/sqlite-autoconf-3170000.tar.gz
$ tar xvzf sqlite-autoconf-3170000.tar.gz
$ cd ./sqlite-autoconf-3170000
$ ./configure
$ make
$ make install
$ source ~/.bash_profile
$ rm -rf sqlite-autoconf-317000*
$ sqlite3 --version
; 3.17.0 2017-02-13.....

Hello World アプリ作成

$ cd ~
$ rails new hello_world
$ cd hello_world
$ vim Gemfile
$ bundle install
- # gem 'therubyracer', platforms: :ruby
+ gem 'therubyracer', platforms: :ruby
$ bundle exec rails s -e production
=> Booting Puma
=> Rails 5.0.2 application starting in production on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.8.2 (ruby 2.4.0-p0), codename: Sassy Salamander
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

apache2.4のインストール

$ sudo yum install httpd24 httpd24-devel

Passengerのインストール

$ gem install passenger
$ rbenv rehash
$ sudo dd if=/dev/zero of=/swap bs=1M count=1024
$ sudo mkswap /swap
$ sudo swapon /swap
$ passenger-install-apache2-module
.....
linking shared-object passenger_native_support.so

--------------------------------------------
Almost there!

Please edit your Apache configuration file, and add these lines:

   LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
   <IfModule mod_passenger.c>
     PassengerRoot /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2
     PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.4.0/bin/ruby
   </IfModule>

After you restart Apache, you are ready to deploy any number of web
applications on Apache, with a minimum amount of configuration!
....

Apacheの設定

$ sudo chown -R apache. ~/hello_world

$ sudo vim /etc/httpd/conf.d/passenger.conf
+   LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
+   <IfModule mod_passenger.c>
+     PassengerRoot /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2
+     PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.4.0/bin/ruby
+   </IfModule>
$ sudo vim /etc/httpd/conf.d/apps.conf
 <VirtualHost *:80>
    ServerAlias ip address
    ServerName ip address

    <Directory "/home/ec2-user/hello_world/public/">
        PassengerAppRoot /home/ec2-user/hello_world
        RailsEnv production        
        Options -MultiViews
        Options Indexes FollowSymLinks
        Require all granted
    </Directory>
 </VirtualHost>

Apacheの起動

$ sudo service httpd start
$ sudo service httpd status

続きを読む

p2インスタンス上にCUDA, cuDNNを用いたChainer環境を構築する

やったこと

AWSのp2.xlargeにCUDA, cuDNNを利用可能なChainer環境を作成してみました。

前提

以下のバージョンを利用しました。(実施時点のものです)

version
OS Ubuntu14.04
pyenv 1.0.8
Anaconda 4.3.0
python 3.6.2
CUDA 8.0
cuDNN v5.1
Chainer 1.2.21

必要なライブラリのインストール

sudo apt-get install -y git gcc make openssl libssl-dev libbz2-dev libreadline-dev libsqlite3-dev

pyenvのインストール

pyenvのインストールは以下を参考にしました。
http://qiita.com/y__sama/items/5b62d31cb7e6ed50f02c

git clone https://github.com/yyuu/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
source ~/.bashrc

Anacondaのインストール

Anacondaのインストールは以下を参考にしました。
http://qiita.com/y__sama/items/5b62d31cb7e6ed50f02c

pyenv install -l | grep anaconda
pyenv install anaconda3-4.3.0
pyenv rehash
pyenv global anaconda3-4.3.0
echo 'export PATH="$PYENV_ROOT/versions/anaconda3-4.3.0/bin/:$PATH"' >> ~/.bashrc
source ~/.bashrc
conda update conda
python --version

CUDAのインストール

wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda-repo-ubuntu1404-8-0-local-ga2_8.0.61-1_amd64-deb
sudo dpkg -i cuda-repo-ubuntu1404-8-0-local-ga2_8.0.61-1_amd64-deb
sudo apt-get update
sudo apt-get install cuda -y
echo 'export CUDA_HOME=/usr/local/cuda' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${CUDA_HOME}/lib64' >> ~/.bashrc
echo 'export PATH=$PATH:${CUDA_HOME}/bin' >> ~/.bashrc

cuDNNのインストール

別途nvidiaのサイトから取得。

mkdir .cudnn
tar xvzf ./cudnn-8.0-linux-x64-v5.1.tgz
mv ./cuda ./.cudnn
mv ./.cudnn/cuda ./.cudnn/5.1
sudo ln -s ~/.cudnn/5.1/include/cudnn.h /usr/local/cuda/include/cudnn.h
sudo ln -s ~/.cudnn/5.1/lib64/libcudnn* /usr/local/cuda/lib64/
sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn*

Chainerのインストール

pip install chainer

動作確認

mkdir mnist
wget https://raw.githubusercontent.com/pfnet/chainer/master/examples/mnist/train_mnist.py ./mnist
# GPU使用
time python ./mnist/train_mnist.py -g 0 -e 10
# GPU不使用
time python ./mnist/train_mnist.py -g -1 -e 10

実行時間に差が出たら成功です。

続きを読む

[EC2] SSH 接続時のログインメッセージにインスタンスタイプとかリージョンとか表示する

対象は Amazon Linux で動いてる EC2。
SSH接続して作業するときに、このインスタンスってどのインスタンスタイプで動いてたっけ?って知りたいときとかに便利。

こんな感じのシェルスクリプトを /etc/update-motd.d/40-instance-meta とかって名前で保存して実行権限与えておいてください。

/etc/update-motd.d/40-instance-meta
#!/bin/sh
instance_type=$(curl -s http://169.254.169.254/latest/meta-data/instance-type/)
az=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
region=$(echo ${az} | sed -e 's/[a-z]$//g')

cat << EOF
 INSTANCE TYPE : ${instance_type}
 REGION : ${region} (${az})

EOF

そのあとで update-motd コマンドを root 権限で実行すると、次回 SSH ログイン時からログインメッセージ変わります。あら便利

Last login: Wed Mar 15 15:05:40 2017 from xxx.xxx.xxx.xxx
      ___         _            __
     / _ | __ _  (_)_ _  ___  / /____
    / __ |/  ' / /  ' / _ / __/ _ 
   /_/ |_/_/_/_/_/_/_/_/___/__/___/

https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/

 Nginx 1.11.10, PHP 7.1.2, Percona MySQL 5.6.35, WP-CLI 1.1.0

 amimoto     https://www.amimoto-ami.com/
 digitalcube https://en.digitalcube.jp/

 INSTANCE TYPE : t2.small
 REGION : ap-northeast-1 (ap-northeast-1c)

続きを読む

Azure と AWS のインスタンス性能比較

概要

クラウドプラットフォームの選択においてコンピュート性能と価格の正しい選定が重要となってきます。このコンピュート性能を表す指標として、Microsoft Azure では ACU (Azure Compute Unit)、Amazon Web Services では ECU (EC2 Compute Unit) という論理的指標を公開しています。今回はこの指標を用いて、Azure VM と AWS EC2 のインスタンス性能の比較をしてみたいと思います。

Azure と AWS コア数の考え方

一般的に Azure の物理ホストサーバーはハイパースレッディングがオフ、AWS のホストサーバーはオン(一部 HPC インスタンス等で例外あり)となっています。ハイパースレッディングがオフの場合、物理コア 1 に対して論理コアは1となりますが、ハイパースレッディングがオンの場合、物理コア 1 に対して論理コアは2となります。すなわち Azure の仮想マシンで2コアのインスタンスは、物理コア2個にマップされますが、AWS EC2 の場合2コアのインスタンスは物理コア1個にマップされます。

つまり同じ CPU スペックのインスタンスを Azure と AWS で比較した場合、仮想コア数が同じであっても Azure のインスタンスの方が高性能だと言えます。(なぜなら倍の物理コアが割り当てられている為!)これを意識せずに単純に仮想コア数でインスタンスタイプを選定し、Azure と AWS で価格比較してしまうと誤った結果を導き出す事となります。

ACU と ECU

そこで登場するのが冒頭で触れた ACU (Azure Compute Unit)ECU (EC2 Compute Unit) になります。ACU は Azure A1 インスタンス (AMD Opteron 4171 HE @ 2.1 GHz) 1仮想コアのパフォーマンスを 100 と定義し、Azure の各種インスタンスはそれとの相対比較として定義されます。例えば同 CPU スペックで仮想コア数が2個の A2 インスタンスは ACU = 200 となります。ECU は m1.small インスタンス (2007 AMD Opteron 1.0~1.2 GHz) を1として定義されています。

ACU / ECU 比率

この ACU と ECU ですが、論理的な指標となるため変化の激しいクラウド界隈において不変の定義となります。このため ACU と ECU の比率も同様に不変となります。

  ACU ≒ 46 × ECU

この式は Azure, AWS それぞれで複数タイプのインスタンスに対して、同じ CPU ベンチマークを実施する事で求めた結果です。この式を使えば例えば AWS EC2 で m3.large インスタンスは 3.75 ECU なので、46 × 3.75 = 172.5 ACU となります。これに相当もしくはより高性能な Azure インスタンスとしては A2v2 = 200 ACU や D1v2 = 210 ACU 等が挙げられます。これを価格面で比較すると次のようになります。

東日本(東京) Linux OS ベースの時間単価比較(EC2 JPY は 114円換算、2017/03/14時点の価格)

インスタンス ACU 時間単価(USD) 時間単価(JPY)
m3.large 172.5 $0.096 10.94円
A2 v2 200 $0.113 11.53円
D1 v2 210-250 $0.102 10.41円

この表を見てわかるように、いずれも比較的同じ価格性能比が示されているのがわかります。また同じ Azure のインスタンスであってもコストパフォーマンスに違いが出ているのがおもしろい結果と言えます。

補足情報

上記の式を導くのに用いた CINBENCH による負荷テスト結果を参考までに掲載しておきます。Azure VM, AWS EC2 の各種インスタンスに対して同一ツールにより負荷をかけることで、負荷テスト結果に対する ACU, ECU の平均値を算出します。

Azure VM ACU CINBENCH ACU/Bench
A2 v2 200 81 2.469136
F1 230 105 2.190476
D3 640 291 2.199313
D4 v2 1840 842 2.185273
H8 2360 1060 2.226415
Avarage 2.254123
AWS EC2 ECU CINBENCH ECU/Bench
m4.xlarge 13 275 0.047273
m4.4xlarge 53.5 1131 0.047303
c4.large 8 156 0.051282
c4.4xlarge 62 1256 0.049363
Avarage 0.048805

小学校で分数どうしの割り算を習うかと思いますが、割る数の分子と分母をひっくり返して掛けるので Bench が打ち消され

  ACU/Bench ÷ ECU/Bench = ACU/Bench × Bench/ECU = ACU/ECU

となります。それぞれの平均値より 2.254123 / 0.048805 = 46.186043 となり

  ACU / ECU ≒ 46

すなわち

  ACU ≒ 46 × ECU

が導き出されます。

続きを読む

初めてのAWSで、WebアプリケーションからプライベートサブネットのRDSへ接続までを実施した。

AWSの人気が非常に高いことは存じ上げておりました。
しかしながら、現職の業務上だと触る機会が皆無であるため、
プライベートで触ろうとしました。

しかし…
AWS、ぜんぜんわかりませーーーん。

何がわからん?

目標

  • RDSを使用して、DBの構築、自作WebアプリケーションからDBアクセスを可能にする。

実施したこと

AWSユーザー設定

  1. アカウントを作成する
  2. 仮想MFAデバイスをインストールする
    • 自分のiPhoneに「Google Authenticator」をインストールする
  3. MFAを設定する
    • 2つの認証コードは、以下

      • 表示された6桁の番号を、「認証コード1」に入力する
      • 約30秒後、続けて表示された6桁の番号を、「認証コード2」に入力する
      • 2行表示されるものだ、と思ってはまった。
  4. MFAを用いてサインインする
  5. グループを作成する
  6. IAMアカウントを作成する
    • キーなどが記載されたCSVをダウンロードできる。紛失しないようにする。
    • 一番最初に作成したアカウントは、ルートアカウントと呼ばれる。それで作業することは、基本的にしない。
  7. パスワードポリシーを設定する
  8. 作成したユーザへMFA割り当てる

AWS 環境構築

アカウント登録は完璧! さあ構築だ! と思ったら…。

  • 「サービスへのお申し込みはあと少しで完了です!」ページが表示されてしまうッッ!!

??? と思いましたが、以下が終わっていませんでした。
* クレジットカードの登録
* 電話により本人確認

アカウント登録、完璧ではありませんでした。
「サービスへのお申し込みはあと少しで完了です!」ページの「AWS への登録を完了してください。」をクリックし、
残りを登録・本人確認をしました。

環境構成

  • VPC

    • パブリックサブネット

      • EC2(踏み台)
    • プライベートサブネット
      • RDS

VPC作成

  1. 項目を入力して作成する

    • ネームタグ

      • vpc-employee-mask-management
    • IPv4 CIDR Block
      • x.x.x.x/xx
    • テナンシー
      • デフォルト
  2. 右クリックして「DNSホスト名の編集」を「はい」にする

サブネット作成

  1. 項目を入力してパブリックサブネットを作成する

    • ネームタグ

      • EM-public-subnet
    • VPC
      • vpc-employee-mask-management
    • アベイラビリティーゾーン
      • ap-northeast-1a
    • IPv4 CIDR Block
      • x.x.x.y/xx
  2. 項目を入力してプライベートサブネットを作成する
    • ネームタグ

      • EM-private-subnet
    • VPC
      • vpc-employee-mask-management
    • アベイラビリティーゾーン
      • ap-northeast-1a
    • IPv4 CIDR Block
      • x.x.x.z/xx
  3. パブリックサブネットの「自動割り当てパブリックIP」を有効化する

インターネットゲートウェイ作成

  1. 項目を入力してインターネットゲートウェイを作成する

    • ネームタグ

      • EM-internet-gateway
  2. インターネットゲートウェイにVPCをアタッチする

ルートテーブル作成

  1. 項目を入力してルートテーブルを作成する

    • ネームタグ

      • EM-root-table
    • VPC
      • vpc-employee-mask-management
  2. ルートタブにて、新しいルートを作成する
    • 送信先

      • 0.0.0.0/0
    • ターゲット
      • EM-internet-gateway

サブネットへルートテーブルを割り当て

  1. サブネット「EM-public-subnet」のルートテーブルタブで、ルートテーブルを割り当てる

セキュリティグループ作成

  1. 項目を入力してパブリック用のセキュリティグループを作成する

    • ネームタグ

      • EM-public-DMZ
    • グループ名
      • EM-public-DMZ
    • 説明
      • Employee Management Public Security Group
    • VPC
      • vpc-employee-mask-management
  2. 項目を入力してプライベート用のセキュリティグループを作成する
    • ネームタグ

      • EM-private-DMZ
    • グループ名
      • EM-private-DMZ
    • 説明
      • Employee Management Private Security Group
    • VPC
      • vpc-employee-mask-management
  3. インバウンドを変更する
    • EM-public-DMZ

      • 1行目

        • タイプ

          • SSH
        • プロトコル
          • TCP
        • ポート
          • 22
        • 送信元
          • マイIP
      • 2行目
        • タイプ

          • PostgreSQL
        • プロトコル
          • TCP
        • ポート
          • 5432
        • 送信元
          • 0.0.0.0/0
    • EM-private-DMZ
      • 1行目

        • タイプ

          • PostgreSQL
        • プロトコル
          • TCP
        • ポート
          • 5432
        • 送信元
          • EM-public-DMZ

DBサブネットグループ作成

  1. RDSを開き、項目を入力し、DBサブネットグループを作成する

    • 名前

      • EM-dbsubnet-mask
    • 説明
      • Employee management DB subnet
    • VPC
      • vpc-employee-mask-management
    • 追加するサブネット
      • ap-northeast-1a

        • アベイラビリティーゾーン

          • ap-northeast-1a
        • サブネットID
          • EM-private-subnetのID
      • ap-northeast-1c
        • アベイラビリティーゾーン

          • ap-northeast-1c
        • サブネットID
          • EM-private-subnet-CのID

RDSインスタンス作成

  1. エンジンを選択する

    • PostgreSQL
  2. インスタンスの仕様を入力する
    • ライセンスモデル

      • postgresql-license
    • バージョン
      • PostgreSQL 9.5.4-R1
    • インスタンスのクラス
      • db.t2.micro
    • DBインスタンス識別子
      • employee-mask-management
    • マスタユーザの名前
      • adminuser
    • マスターパスワード
      • ********
    • パスワードの確認
      • ********
  3. 詳細設定の設定
    • VPC

      • vpc-employee-mask-management
    • サブネットグループ
      • EM-dbsubnet-mask
    • パブリックアクセス
      • いいえ
    • アベイラビリティーゾーン
      • 指定なし
    • VPCセキュリティグループ
      • EM-private-DMZ
    • データベースの名前
      • employeemanagement
    • データベースのポート
      • 5432
    • DB パラメータグループ
      • default.postgres9.5

EC2インスタンス作成

  1. マシンイメージを選択する

    • Amazon Linux AMI 2016.09.1 (HVM), SSD Volume Type
  2. インスタンスタイプを選択する
    • t2.micro
  3. インスタンスの設定をする
    • インスタンス数

      • 1
    • ネットワーク
      • vpc-employee-mask-management
    • サブネット
      • EM-public-subnet
    • 自動割り当てパブリックIP
      • サブネット設定を使用
  4. ストレージの設定をする
    • 全てデフォルトのまま
  5. Add Tags
    • Name

      • EM-Server
  6. セキュリティグループの設定をする
    • 既存のセキュリティグループを選択する

      • EM-public-DMZ
  7. キーペアを作成、ダウンロードする
    • キーペア名

      • EM_Server_Key

EC2インスタンスへの接続

  • ssh -i {key file name} {your user name}@{your address.com}

ローカルの開発環境にあるWebアプリからRDSへ接続する方法

  • Webアプリ側で、JSchを使用して、SSHポートフォワーディングする

結果

  • ローカルのWebアプリから、プライベートサブネットのRDSへ接続することができた。

わからないこと

  • EC2によるポートフォワーディングが、AWS上に存在しないアプリケーションがRDSへ接続する方法として、適切なのか?

    • 別のAWSサービスを利用した、もっとよい方法があるのではないか?

参考にさせて頂きました

続きを読む

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


以上になります。

続きを読む

AWS Lambda (Node.js 4.3) で PhantomJS + WebdriverIO を使用してヘッドレスWebブラウザを操作する

目的

  • 社内グループウェア (Aipo) での出勤操作を自動化したかったんだ….

    • Amazon Dash ボタン + maddox/dasher + API Gateway + Lambda で社内グループウェア へログイン → 出勤ボタンをクリック
    • LT用のネタとして

構成

AWS Lambda (Node.js 4.3)

下記の設定でひとまず動作した。

  • メモリ: 512MB
  • タイムアウト: 3分

コードは npm でパッケージ管理を行い、Apex でデプロイを行った。

PhantomJS

実際には、下記のprebuiltなnpmモジュール: phantomjs-prebuilt を使用した。

開発時・デプロイ時は、このビルド済み PhantomJS バイナリをデプロイパッケージに含めて Lambda へアップロードするため、数十秒〜1分程度かかるつらみがある…..

WebdriverIO

phantomjs-prebuiltのREADME を見ると、下記の2通りの実行方法が記載されている。

  • Running via node: PhantomJS に処理を記載したjsファイルを食わせる方法
  • Running with WebDriver: Selenium WebDriver のプロトコルを介して (WebdriverIO から) 操る方法

主観的に、なんとなく WebdriverIO を使用した記述方法のほうが見通しが良さそうだったのでこっちの方法を選択した。

準備

# package.json
---
.
.
.
"dependencies": {
  "phantomjs-prebuilt": "^2.1.14",
  "webdriverio": "^4.6.2"
}
.
.
.
---
$ env PHANTOMJS_PLATFORM="linux" PHANTOMJS_ARCH="x64" npm install

phantomjs-prebuilnpm install 時にシェルの実行環境を判定してふさわしいバイナリをダウンロードするらしい。

そのため、

macOS でデプロイパッケージ作成

Lambda (Amazon Linux? x64) へアップロード

などの状況の場合は、下記の説明のとおりに環境変数を設定して、Linux x64 用のバイナリをダウンロードさせる (もしくは、Linux x64 な環境で作業を行う)。

If you know in advance that you want to install PhantomJS for a specific architecture, you can set the environment variables: PHANTOMJS_PLATFORM (to set target platform) and PHANTOMJS_ARCH (to set target arch), where platform and arch are valid values for process.platform and process.arch.
Cross-Platform Repositories

Lambda関数

Running with WebDriver に従って記述。

'use strict';

const phantomjs = require('phantomjs-prebuilt');
const webdriverio = require('webdriverio');

const webDriverOpts = {
    desiredCapabilities: {
        browserName: 'phantomjs',
        logLevel: 'verbose',
        host: 'localhost',
        port: '4444'
    }
};

exports.handler = (event, context, callback) => {
    phantomjs
        .run('--webdriver=4444')
        .then((phantom) => {
            const client = webdriverio.remote(webDriverOpts).init();
            return client
                .url("http://example.com/aipo")
                .waitForExist('.button[value="ログイン"]', 10000)
                .setValue('#member_username', "ユーザー名")
                .setValue('#password', "パスワード")
                .click('.button[value="ログイン"]')
                .waitForExist('=Aipo', 10000)
                .click('//input[@type="button" and (@value="出勤" or @value="退勤")]')
                .pause(5000)
                .then(() => {
                    phantom.kill();
                    return Promise.resolve();
                })
                .catch((err) => {
                    console.error(`error: ${err}`);
                    phantom.kill();
                    return Promise.reject(err);
                });
        })
        .then(() => {
            console.log("done");
            context.succeed({
                statusCode: 200,
                body: JSON.stringify({ "message": "done" })
            });
        })
        .catch((err) => {
            console.error(`failed: ${err}`);
            context.fail({
                statusCode: 500,
                body: JSON.stringify({ "message": `failed:${err}` })
            });
        });
};

実際に書いたコード

hiraro/lambda-phantomjs

続きを読む