2017年5月27日土曜日

AWS EC2 で Amazon Linux を使用する場合に最初にやること


オフィス狛 技術部です。

AWSでEC2インスタンスを立ち上げる度に、設定すべき事を忘れてしまうので、
備忘録の為にブログに残しておこうと思います。

「aws ec2 やる事」でググると、もっと細くやる事を記載している方も居るので、
しっかりやりたい方は、それらを参考にして下さい。

ここに記載するのは、本当に最低限の設定です。

何はともあれ yum update

まずは yum update です。
$ sudo yum update -y

タイムゾーンの変更

Amazon Linux はデフォルトのタイムスタンプが UTC になっているので、日本時間に変更します。
$ date
2017年  5月  20日 土曜日 02:33:43 UTC
$ sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
$ date
2017年  5月  20日 土曜日 11:35:33 JST

このままでは、サーバーの再起動を行うと UTC に戻ってしまうので、設定を変更します。
$ sudo vim /etc/sysconfig/clock

※「ZONE="UTC"」となっている部分を「ZONE="Asia/Tokyo"」に変更します。

ec2-user の 削除

ec2-user はデフォルトのユーザーなので、セキュリティ上、削除しておいた方が良いです。
(もちろん変わりのユーザーも作成します。)

まずは、新規ユーザー(ofkomauser)を追加します。
$ sudo useradd ofkomauser

鍵などをec2-userからコピーし、権限設定を行います。
$ sudo cp -arp /home/ec2-user/.ssh /home/ofkomauser/
$ sudo chown -R ofkomauser /home/ofkomauser/.ssh

新しいユーザーのパスワードを設定します
$ sudo passwd ofkomauser

sudoersファイルを編集し、新しいユーザーで sudo が使えるようにします。
$ sudo visudo -f /etc/sudoers.d/cloud-init
※「ec2-user」の部分をコメント化、もしくは削除し、新しいユーザーを追加します。
#ec2-user ALL = NOPASSWD: ALL
ofkomauser ALL = NOPASSWD: ALL

# User rules for ec2-user
#ec2-user ALL=(ALL) NOPASSWD:ALL

そして、最後に「ec2-user」を削除します。
$ sudo userdel -r ec2-user

以上が、最低限の設定になります。

EC2の用途によっては、もっと細かい設定は必要ですが、
それはまった追って記事にしようと思います。


,

2017年5月6日土曜日

Codeigniter で他システムとセッション共有する場合に、$_SESSION が消えてしまう

オフィス狛 技術部です。

同じドメイン配下に存在する別システム(Codeigniter ではないPHPを使ったシステム)と、
セッションを共有する・・・つまり、$_SESSION で値をやりとりする事になったのですが、
Codeigniter 側で $_SESSION から値を取ろうとすると、空になっている現象が発生しました。
単純には行かないのですね、やっぱり。

※ちなみに、Codeigniter のバージョンは 3.1.2 です。

1)セッションIDを調べてみる

セッションIDが変わってしまって、違うセッションを見ているのかと思い、
$_COOKIE を調べたところ、session_id は同じでした。
(同じドメインですし、敢えてセッションを変えるような設定もしていないので)
セッションIDが同じで値が取れないのであれば、どこかで値を消している、という事になります。

2)Codeigniter を使用していないPHPシステムで試してみる

Codeigniter を使用していない、Webサーバー上に置いただけのPHPファイルで試してみたところ、
$_SESSION から値が取れる事が確認出来ました。
という事は、Codeigniter でセッションを消している事になります。

3)Codeigniter の Session.php を調べてみる

というわけで、Codeigniter の System/libraries/Session に存在する、
「Session.php」を調べてみました。

105行目付近に、
$class = $this->_ci_load_classes($this->_driver);
という記述があり、このタイミングで driver を変更しているため、
この処理以降に設定した driver のセッションを参照しているのではないかと予想できます。

※実際、この処理の直前では $_SESSION の値は取得できたが、
直後には値が消えていた事を確認しています。

4)対応方法(暫定)

とりあえず、先程の記述の直前で、
// 追加ここから
session_start();
$session = $_SESSION // 値を一時変数に格納
session_write_close();
// 追加ここまで
$class = $this->_ci_load_classes($this->_driver);
と記載します。実は、session_start 自体はもっと後で行うのですが、
ここで一旦スタートさせて、$_SESSION の値を退避しておきます。
(closeも忘れずに)
そして、今度は、元々あった「session_start」(150行目近辺)の後に、
session_start();
//追加ここから
$_SESSION = $session; // 値を一時変数から再設定
// 追加ここまで
と追加する事で、他システムで設定された $_SESSION を利用する事ができます。

5)本当はどう対応するべきなのか

実は、弊社側のシステムは、セッションをDBに保存する設定にしています。
そしておそらく、他システム側は、セッションはファイル保存していると思います。
(弊社保有のシステムでは無いので、実際は分からないですが・・・)
ですので、$_SESSION が消えてしまうのも、当然と言えば当然なのです。
だとするならば、
こちら側の driver を「files」にして、保存パスを共有した場合、うまくいきそうな気がします。

※ちなみに、設定出来る driver は、本家サイト(翻訳版)に記載してありますが、
  • files(デフォルト; ファイルシステムベース)
  • database
  • redis
  • memcached
となっています。(自分でカスタムセッションドライバも作成可能です。)

今回は、テスト環境含め、弊社が触れる環境では無いので、
色々試せないですが、いずれ社内に環境作って試してみようと思います。


,

2017年4月21日金曜日

Jenkins をアップデートしたら、 HTTP ERROR 503 Service Unavailable が発生

オフィス狛 技術部です。

Jenkinsは、頻繁にアップデートが出るので、本番稼働中だったりするとなかなか反映できません。
今回も、以下のようなアラートが出たので、まずは開発環境からアップデートする事に。

弊社の場合、JenkinsをインストールしているのはAWS EC2なので、一番簡単なアップデートは、
sudo yum update です。
そして、アップデートした後に、接続してみると・・・
HTTP ERROR 503 Service Unavailable」になりました。


・・・・だから、嫌なんですよ、この類のアップデート。と愚痴っても仕方ないです。
いきなり本番環境をアップデートしなかった事をラッキーと思わないと。
(いや、しないですけどね、そんな事)

ログを確認すると、以下の内容でした。
UnsupportedClassVersionError」?
何かが古い?
WARNING: Failed startup of context w.@a38dddf{/,file:/var/cache/jenkins/war/,STARTING}{/var/cache/jenkins/war}
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.eclipse.jetty.webapp.IterativeDescriptorProcessor.visit(IterativeDescriptorProcessor.java:85)
 (中略)
        at Main._main(Main.java:264)
        at Main.main(Main.java:112)
Caused by: java.lang.UnsupportedClassVersionError: jenkins/util/SystemProperties : Unsupported major.minor version 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:803)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
 (中略)
        at org.eclipse.jetty.server.handler.ContextHandler.loadClass(ContextHandler.java:1583)
        at org.eclipse.jetty.webapp.StandardDescriptorProcessor.visitListener(StandardDescriptorProcessor.java:1956)
        ... 25 more

色々と調べてみると、本家に issue がありました。
Jenkins 2.54 causes java.lang.UnsupportedClassVersionError

なるほど、Java 8が必須になったのですね。
確かに、EC2 の Amazon Linux はデフォルト Java 7 です。

と言うわけで、Javaのアップデートをしていきます。

まずは現在のバージョンを改めて確認してみます。
$ java -version
java version "1.7.0_131"
OpenJDK Runtime Environment (amzn-2.6.9.0.71.amzn1-x86_64 u131-b00)
OpenJDK 64-Bit Server VM (build 24.131-b00, mixed mode)
$ yum list installed | grep java
java-1.7.0-openjdk.x86_64             1:1.7.0.131-2.6.9.0.71.amzn1 installed    
javapackages-tools.noarch             0.9.1-1.5.amzn1              installed    
tzdata-java.noarch                    2017b-1.69.amzn1             installed

Java 7 である事が確認出来たので、次は Java 8 にアップデートします。
$sudo yum install java-1.8.0-openjdk-devel

alternatives でバージョンを切り替えます。
$ sudo alternatives --config java

2 プログラムがあり 'java' を提供します。

  選択       コマンド
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:2

念の為、適用されているバージョンを確認します。
$ java -version
openjdk version "1.8.0_121"
OpenJDK Runtime Environment (build 1.8.0_121-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

無事にアップデート出来たので、Jenkinsの再起動を行います。
再起動は
http://[ホスト名 or IPアドレス]/safeRestart
に接続する事で、簡単に行う事ができます。

これで無事に接続できました。
アップデートの度にこんな思いするのは、本当辛いです。


, ,

2017年4月2日日曜日

Redmine チケットの状態変更をSlackに連携する

オフィス狛 技術部です。

オフィス狛では、社内の連絡に関してメールは使わず、全てSlackでやり取りしています。
ただ、Redmineに関してはデフォルトだとメール通知しか出来ないので、
Redmineの為だけにメールチェックを行う事が必要になってしまいます。
このメールチェックをやめたい、という事で、
Redmineでチケットが登録・編集された際に、Slackへ通知するようにしていきます。

まずは、Slackのチャンネル作成、Slack側のアプリ作成作業を行う必要があるのですが、
この辺の作業は、下記を参照下さい。
AWS EC2 にインストールした GitLab と Slack の連携
という事で、この先は、Slackのチャンネル作成(今回は、「#redmine-notification」というチャンネルを作成)が完了していて、
且つ、Slack側のアプリ作成〜Webhook用のURL取得済みとして、進めて行きます。

1)redmine_slack(プラグイン)のインストール

まずはRedmineのプラグインをインストールします。
$ cd /var/lib/redmine/plugins
$ git clone https://github.com/sciyoshi/redmine-slack.git redmine_slack
$ bundle install
$ rake redmine:plugins:migrate RAILS_ENV=production
$ touch /var/lib/redmine/tmp/restart.txt
ここでは、redmineのホームディレクトリが「/var/lib/redmine」の場合を想定しています。
「restart.txt」を作成しておく事で、apacheの再起動無しで、Redmineの再起動を行う事ができます。
(ファイル作成後に、ブラウザでRedmineに接続した時に再起動が行われます。)

正しくインストールされていると、Redmineの「管理 > プラグイン」から、以下のように確認出来ます。


2)カスタムフィールドの作成

続いて、Redmineの「管理 > カスタムフィールド」で「プロジェクト」を選択し、
新しいカスタムフィールドを作成します。
名称は「Slack Channel」にしておきます。


3)redmine_slack(プラグイン)の設定

Redmineの「管理 > プラグイン」から、Redmine Slackの「設定」を選択します。
以下のような設定画面が出てくるので、
Slack側のアプリを作成した際のWebhook用URLを入力します。
今回は、チケットの更新のみを連携の対象としています。(「Post Issue Updates?」にチェックを付けている)


そして、先ほどカスタムフィールドの名前を「Slack Channel」にした意味がここで分かります。
redmine_slackは、デフォルトだと、全プロジェクトで共通な一つのチャンネルのみ連携が出来ます。

その連携先チャンネルをプロジェクト単位で決めたい場合は、
カスタムフィールドで「Slack Channel」を作成する必要があります。

ちなみに、プラグイン設定の方の「Slack Channel」は必須項目で、
指定無しには出来ないので、「-(半角ハイフン)」を設定しています。

4)Redmine各プロジェクトごとの設定

プロジェクトの「設定 > 情報」を表示すると、追加したカスタムフィールドが出てくるので、
ここに、Slackのチャンネル(今回は、「#redmine-notification」)を入力します。


これで、このプロジェクトのチケットが変更された際、Slackへの連携が可能になります。


備考:連携の際に気を付ける事

弊社の場合、Git(GitLab)のリポジトリにPushした際にSlackへ通知が飛び、
同時に、リポジトリへの変更がRedmineが参照しているリポジトリへ連携されるようになっています。
つまり、コミットコメントなどで、Redmineのチケットの状態を変更するような記載(「fixes #xxxx」とか)をすると、 Slackの通知が2回飛ぶ事になってしまい、これが開発繁忙期になると、通知がピーピー鳴りまくるので、 通知が必要なプロジェクトを見極める必要があります。

弊社では、お客様とのやりとり(バグトラッキング)で、通知は漏れなく欲しい場合、
そして、そのプロジェクトでGitのリポジトリを参照していない場合のみ、
今回のような設定を行なっています。


,

2017年3月25日土曜日

AWS EC2上のGitLabリポジトリを別サーバーのRedmineから参照する

オフィス狛 技術部です。

早くもGitLab のインストール・設定シリーズ第5回目となります。
第3回でリポジトリを移行した結果、ある問題が発生しました。

移行前のリポジトリは Redmine がインストールしてあるサーバーに存在したので、
Redmine からリポジトリを参照出来たのですが、別サーバーに移行したら参照する事が出来なくなりました。

そう、「Redmineは、自サーバーにあるリポジトリのみ参照可能」なのです。

折角 GitLab に移行したのだから、GitLab の issue を使う、という事も考えたのですが、
Redmine の工程管理(チケット)とリポジトリの関連付けはやっぱり便利なんですよね・・・・。

というわけで、今回は、AWS EC2 上の GitLab リポジトリを Redmine から(間接的に)参照出来るようにしたいと思います。

その他の記事は、以下をご覧下さい。
第1回(AWS EC2 に GitLab をインストールする)
第2回(AWS EC2 に GitLab をインストールする(メールの送信テスト編))
第3回(AWS EC2 の GitLab へリポジトリを移行する)
第4回(AWS EC2 にインストールした GitLab と Slack の連携)

1)GitLab のリポジトリをクローンする

現在、メインのリポジトリは、GitLab側のEC2(サーバーA)になるので、
そこから、Redmine側のEC2(サーバーB)へリポジトリをクローンします。

【注意】クローンする場所(ディレクトリ)に対しては、Redmine が動作しているWebサーバー(ここではApache)が参照権限を持っている必要があります。

では、サーバーAのリポジトリをクローンします。
sshで接続するので、サーバーAの鍵が必要になります。鍵は、「/home/[ec2_user]/.ssh」に格納します。
※[ec2_user]は、サーバーBにログインしているユーザーで置き換えて下さい。

そして、コマンドを簡略化させる為に、同じディレクリに「config」というファイルを作成し、
Host gitlab_git
  User git
  HostName gitlab.hogehoge.co.jp
  IdentityFile ~/.ssh/keyname
上記の内容を記述します。

 Host : コマンドに含める簡略名になります。 
 User : 接続するサーバーのユーザー
 HostName : 接続するサーバー
 IdentityFile : 使用する鍵

これで準備が出来たので、早速クローンします。
$ git clone --bare ssh://gitlab_git/hogehoge-dev/RipoTest.git
「hogehoge-dev」はGitLab側のユーザー or グループになります。

クローンしたリポジトリは、対象となるRedmineプロジェクトから「設定 > リポジトリ」で参照出来るように設定しておきます。


2)redmine_github_hookのインストール

ここでRedmineのプラグインをインストールします。
$ cd /var/lib/redmine/plugins
$ git clone git://github.com/koppen/redmine_github_hook.git
$ touch /var/lib/redmine/tmp/restart.txt
ここでは、redmineのホームディレクトリが「/var/lib/redmine」の場合を想定しています。
「restart.txt」を作成しておく事で、apacheの再起動無しで、Redmineの再起動を行う事ができます。
(ファイル作成後に、ブラウザでRedmineに接続した時に再起動が行われます。)

正しくインストールされていると、Redmineの「管理 > プラグイン」から、以下のように確認出来ます。

3)GitLab の Webhooks の設定

続いて、GitLab側で Webhooks の設定を行います。
連携したいプロジェクト(リポジトリ)を選択し、右上の歯車プルダウンから「Webhooks」を選択します。


そして、次の設定画面のURLに
http://redmine.hogehoge.co.jp/github_hook?project_id=redmine_identifier
のように入力します。それぞれの環境に合わせて、「redmine.hogehoge.co.jp」と「redmine_identifier」を変更してください。

 redmine.hogehoge.co.jp : Redmine側のEC2(サーバーB)のドメイン or IPアドレス。 
 redmine_identifier : Redmineにおけるプロジェクトの識別子

Triggerも適宜チェックつけますが、単純な連携であれば、Pushだけでも良いかもしれません。


これで、GitLabへのPushはRedmineが参照しているリポジトリに連携され、常に最新状態が保てる事になります。
(何だか二度手間になっている感は拭えないですが)


, , , , ,

2017年3月12日日曜日

AWS EC2 にインストールした GitLab と Slack の連携

オフィス狛 技術部です。

GitLab のインストール・設定シリーズ第4回目の今回は、Slack との連携を行います。

その他の記事は、以下をご覧下さい。
第1回(AWS EC2 に GitLab をインストールする)
第2回(AWS EC2 に GitLab をインストールする(メールの送信テスト編))
第3回(AWS EC2 の GitLab へリポジトリを移行する)

では、早速やり方を説明して行きますが、前回までと違い、今回はブラウザ側の設定で完結出来ます。

1)通知用のチャンネルを作る(Slack側作業)

1-1) 作業の目標はGitLabへのPushをSlackに連携する事なので、
何はともあれ通知用のチャンネルを作成します。


2)通知用のアプリを作る(Slack側作業)

2-1) Slackのメイン画面で、チーム名の横にある「下矢印」をクリックし、メニュー表示後、
「Apps & integrations」を選択します。


2-2) 新たに表示された画面の右上「Build」をクリックします。


2-3) さらに新たな画面に遷移するので、そこで画面中央の「Start Building」をクリックします。


2-4) 続いて、アプリ名を入力し、通知するチームを選択してアプリを作成します。


2-5) アプリが作成されるので、アプリの種類として「Incoming Webhooks」を選択します。


2-6) 「Incoming Webhooks」の設定画面になるので 、まず右上のスイッチを「ON」にし、
その後、「Add New Webhooks to Team」をクリックします。


2-7) 通知を行うチャンネルとして、先程作成したチャンネルを選択し、「Authorize」をクリックします。


2-8) 先程の画面に戻りますが、Webhook用のURLが表示されるので、これをコピーしておきます。


3)通知の設定を行う(GitLab側作業)

ここからは、GitLab側での設定になります。

3-1) 管理者でログインし、管理トップの画面から、右上の歯車から「Service Templates」を選択します。


3-2) Slackを選択


3-3) まず設定を有効にする為、「Active」にチェックを付けます。
後は、どのトリガーで通知を行うか設定します。
下記画像だと、「Push」、「Merge request(GitHubで言う所のPullRequest)」「Tag push」の時に、
「gitlab-notification」へ通知するようにしています。


そして、先程コピーしておいたWebhook用のURLを設定し、「Save」します。
※「Service Templates」で設定しておくと、全てのプロジェクトに反映されるので、便利です。


3-4) 続いて、各プロジェクトで設定を確認してみます。
プロジェクトのトップ画面で、右上の歯車から「Service」を選択します。


3-5) Slackを選択します。
※「Service Templates」で Activeにしているので、緑のマーク(有効マーク)が付いているはずです。


3-6) 設定自体は、「Service Templates」と同じになっていると思いますので、
「Test setting」を押して、通知のテストをしてみましょう。


3-7) 下記のようなメッセージがSlackに出てくれば成功です。
(テスト通知の場合、直近のPush情報が通知されるようです。)

※ちなみに、Slackに表示される画像と名称は、Slack側のアプリ側で変更可能です。
上記は変更後の状態です。
何も設定していないと、Slack API のアイコンが表示されるはずです。


以上です。
開発初期などは、通知が飛び交う事になるので、ちょっとウザいと感じるかもしれません。
(弊社でも最初はそう感じました)
ただ、慣れてくると、誰がいつどんなPushを行なったのか、どの不具合の修正が行われたのか、
など、かなり有意義に思えてきます。
特に、管理する立場から言うと、進捗状況がリアルタイムで分かるのは、かなり助かります。

余談(設定方法はコロコロ変わる)

実は、この記事は、少し前に記載は終わっていて、後は公開するだけの状態でした。
今回、公開しようと思って、情報の確からしさをチェックしたら、
Slack側の設定方法がガラッと変わっていた為、
書き直しをする事になってしまいました。
ブログの記事は寝かしちゃいけないな、と反省しました。


, , , , ,

2017年2月25日土曜日

Codeigniterのupdate_batchで複数条件指定する時の罠。(whereメソッドに注意編)

オフィス狛 技術部です。

前回に引き続き、Codeigniterの「update_batch」について説明していきます。

前回は、下記の様な例で「update_batch」の説明をしました。
$data = array(
   array(
      'emp_id' => 'A001' ,
      'name' => 'koma taro' ,
      'tel' => '090-0000-1111'
   ),
   array(
      'emp_id' => 'A002' ,
      'name' => 'koma saya' ,
      'tel' => '090-0000-2222'
   )
);
$this->db->update_batch('emptable', $data, 'emp_id');

「emptable」(社員テーブル)が更新対象のテーブル名、
「data」が更新&条件用の配列、
「emp_id」(社員ID)が更新時に条件として使用するカラムの指定、となります。

さて、ここで例えば、社員テーブルには、年度ごとにデータが格納されているとします。
(「year」(年度)というカラムがある)
つまり、2016年度と2017年度だけを考えても社員ID「A001」は2件存在する事になります。
でも、更新対象としたいのは2017年度のデータだけだとしたら・・・???

「$this->db->update_batch('emptable', $data, 'emp_id');」の第三引数は、
1つしか指定できません。

さて、困った、という事で、調べてみると、下記の情報が見つかりました。
stackoverflow - Codeigniter update_batch() with included update of the where key
上記のベストアンサーは、「Codeigniterのupdate_batchを拡張しないと無理だよ」と言っています。
さすがにそれは影響範囲大きいなぁ、と思っていたところ、ベストアンサー以外の回答で、
$this->db->where('option1', $option1);
$this->db->update_batch('table_name', $data, 'option2');
とあり、「これだったらお手軽だ」と思って、試してみました。
【注意】結論から言うと、上記では正常に動作しないので、決して真似しないで下さい。

$data = array(
   array(
      'emp_id' => 'A001' ,
      'name' => 'koma taro' ,
      'tel' => '090-0000-1111'
   ),
   array(
      'emp_id' => 'A002' ,
      'name' => 'koma saya' ,
      'tel' => '090-0000-2222'
   )
);
$this->db->where('year', '2017');
$this->db->update_batch('emptable', $data, 'emp_id');
すると、生成されたSQLは、
UPDATE `emptable` SET `name` = CASE
WHEN `emp_id` = 'A001' THEN 'koma taro'
WHEN `emp_id` = 'A002' THEN 'koma saya'
ELSE `name` END,
`tel` = CASE
WHEN `emp_id` = 'A001' THEN '090-0000-1111'
WHEN `emp_id` = 'A002' THEN '090-0000-2222'
ELSE `tel` END
WHERE `year` = '2017'
AND `emp_id` IN ('A001','A002')
となり、年度の条件も付いているので、万々歳、と言う事で、
この方式を採用する事にしました。

やっぱりダメだった


ところが、しばらくして、この処理を実行すると、
2017年度のデータと一緒に、2016年度のデータも更新されている事が判明しました。
しかも、更新されない事もある、との事。

何度か試してみると、確かに生成SQLに「`year` = '2017'」が含まれていない事がありました。

ここで改めてリファレンスと読んでみると、
..note:: $batch_size より多くの行数が渡された場合、 複数のクエリが実行され、それぞれ $batch_size のフィールド/値ペア の分だけ操作を行う。

Codeigniter『データベースリファレンス - クエリビルダクラス - update_batch 』より。
とあります。
・・・・「複数のクエリが実行される」???

先の例だと、例えば1000人の社員を一気に更新する際は、
単純に1000 ÷ batch_size(デフォルトは100)の 10 回クエリが実行されるようです。

しかも、
「$this->db->whereで指定した条件は、最初の1回目のクエリにしか反映されない」
という事のようです。

つまり、最初の1回目のクエリのみ2017年度の社員が更新されて、
2回目以降は2016年度と、2017年度の社員が両方更新されてしまっていたようです。

・・・・いや、どうせだったら、最初の1回目も条件反映しないでよ。

という事で、今回は「update_batch」を使うのをやめて、
「update」複数回実行する、という実装に変更しました。

・・・まあ、テーブル設計が杜撰なのは認めます。
きっと「update_batch」は、そういうテーブル設計を想定していないのだと思います。

「update_batch」を使う際は、ご注意下さい。

【追記】

タイトル詐欺になってしまうので、補足しておくと、
基本は、「update_batch」で複数条件指定は出来ないと思います。
やるならば、先に記載した通り、
Codeigniterのupdate_batch(system/database/drivers/DB_query_builder.php)を
拡張する事になります。


, ,

2017年2月18日土曜日

Codeigniterのupdate_batchで複数条件指定する時の罠。(update_batchの仕組み編)

オフィス狛 技術部です。

Codeigniterのクエリビルダクラスには便利なメソッドが多くありますが、
その中でも特殊なのが「update_batch」です。

複数レコードへの更新が発生する際、
ループで回し、update文を複数回実行する事も可能ですが、
この「update_batch」を使えば、一回の処理で済ませる事が出来ます。

例えば、更新用に以下の様な配列を作成したとします。
$data = array(
   array(
      'emp_id' => 'A001' ,
      'name' => 'koma taro' ,
      'tel' => '090-0000-1111'
   ),
   array(
      'emp_id' => 'A002' ,
      'name' => 'koma saya' ,
      'tel' => '090-0000-2222'
   )
);

そして、update文を実行する際は、
$this->db->update_batch('emptable', $data, 'emp_id');

となります。

「emptable」(社員テーブル)が更新対象のテーブル名、
「data」が先程作成した配列、
「emp_id」(社員ID)が更新時に条件として使用するカラムの指定、となります。

つまり、
  • 社員テーブルの社員IDが「A001」の名前を「koma taro」に、電話番号を「090-0000-1111」に変更する。
  • 社員テーブルの社員IDが「A002」の名前を「koma saya」に、電話番号を「090-0000-2222」に変更する。
という処理を行う、という事です。

普通に考えると、update文を2回実行しないといけない様に思えます。
しかし、「update_batch」の呼び出しは1回です。
一体内部ではどんなSQLに展開しているのでしょうか?
・・・という事で、実際に実行されるSQLを調べてみると、
UPDATE `emptable` SET `name` = CASE
WHEN `emp_id` = 'A001' THEN 'koma taro'
WHEN `emp_id` = 'A002' THEN 'koma saya'
ELSE `name` END,
`tel` = CASE
WHEN `emp_id` = 'A001' THEN '090-0000-1111'
WHEN `emp_id` = 'A002' THEN '090-0000-2222'
ELSE `tel` END
WHERE `emp_id` IN ('A001','A002')

なんともトリッキーですが、確かにこれだとSQL文は1つで済みます。
update文を何度も実行するよりは、処理的にも早いと思います。

でも、ここでふと疑問が。
複数条件指定する場合はどうするのだろう?

ここに罠が潜んでいるのですが、ちょっと長くなってしまったので、
次回に持ち越します。

次回:Codeigniterのupdate_batchで複数条件指定する時の罠。(whereメソッドに注意編)


, ,

2017年2月6日月曜日

CocoaPodsでAbort trap: 6が発生した場合の対処方法。

オフィス狛 技術部です。

iOSアプリのアーカイブ(リリース準備)作業をしている時に、
ある開発端末で「pod update」を行なったら、「Abort trap: 6」が発生し、
Rubyが強制終了しました。
$ pod update
Analyzing dependencies
Downloading dependencies
Installing xxxxxx (1.1.11)
Installing yyyyyy (2.2.22)
Generating Pods project
Abort trap: 6

エラーの詳細(レポート)を見ると、
Application Specific Information:
abort() called
terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error getting value for key 'initializationClass' of extension 'Xcode.DVTKitDFRSupport.Initializer' in plug-in 'com.apple.dt.IDE.DVTKitDFRSupport''
ProductBuildVersion: 8B62
と、色々出ていますが、
こういう時は、大抵「CocoaPods」のバージョンが古い事が原因だったりします。
(実際、エラーが発生した端末では、しばらく開発作業していませんでした。)

何はともあれ、CocoaPodsのバージョンアップをしてみます。
$ sudo gem update cocoapods
Updating installed gems
Updating cocoapods
Fetching: ruby-macho-0.2.6.gem (100%)
Successfully installed ruby-macho-0.2.6
Fetching: fourflusher-2.0.1.gem (100%)
Successfully installed fourflusher-2.0.1
Fetching: nanaimo-0.2.3.gem (100%)
Successfully installed nanaimo-0.2.3
Fetching: claide-1.0.1.gem (100%)
Successfully installed claide-1.0.1
Fetching: CFPropertyList-2.3.5.gem (100%)
Successfully installed CFPropertyList-2.3.5
Fetching: xcodeproj-1.4.2.gem (100%)
ERROR:  While executing gem ... (Errno::EPERM)
    Operation not permitted - /usr/bin/xcodeproj
・・・エラーになってしまいました。
これは、macのOSがEl Capitan以降の場合、rootlessというセキュリティ機能があり、
一部のディレクトリ(/usr、/sbin、/System)に対しては、
root権限でも編集出来なくなっているからです。

仕方無いので、CocoaPodsを再インストールしたいと思います。
まずはアンイストールから。
$ sudo gem uninstall cocoapods
Remove executables:
 pod, sandbox-pod

in addition to the gem? [Yn]  Y
Removing pod
Removing sandbox-pod
Successfully uninstalled cocoapods-1.1.0.beta.1

続いて再インストールします。
インストールするディレクトリを指定しているところがポイントです。
$ sudo gem install -n /usr/local/bin cocoapods
Successfully installed xcodeproj-1.4.2
Fetching: molinillo-0.5.5.gem (100%)
Successfully installed molinillo-0.5.5
Fetching: cocoapods-trunk-1.1.2.gem (100%)
Successfully installed cocoapods-trunk-1.1.2
Fetching: cocoapods-downloader-1.1.3.gem (100%)
Successfully installed cocoapods-downloader-1.1.3
Fetching: cocoapods-deintegrate-1.0.1.gem (100%)
Successfully installed cocoapods-deintegrate-1.0.1
Fetching: cocoapods-core-1.2.0.gem (100%)
Successfully installed cocoapods-core-1.2.0
Fetching: cocoapods-1.2.0.gem (100%)
Successfully installed cocoapods-1.2.0
Parsing documentation for xcodeproj-1.4.2
Installing ri documentation for xcodeproj-1.4.2
Parsing documentation for molinillo-0.5.5
Installing ri documentation for molinillo-0.5.5
Parsing documentation for cocoapods-trunk-1.1.2
Installing ri documentation for cocoapods-trunk-1.1.2
Parsing documentation for cocoapods-downloader-1.1.3
Installing ri documentation for cocoapods-downloader-1.1.3
Parsing documentation for cocoapods-deintegrate-1.0.1
Installing ri documentation for cocoapods-deintegrate-1.0.1
Parsing documentation for cocoapods-core-1.2.0
Installing ri documentation for cocoapods-core-1.2.0
Parsing documentation for cocoapods-1.2.0
Installing ri documentation for cocoapods-1.2.0
7 gems installed
無事に再インストール完了しました。
では、改めて「pod update」します。
$ pod update
Update all pods
Re-creating CocoaPods due to major version update.
Updating local specs repositories
Analyzing dependencies
Downloading dependencies
Installing xxxxxx (1.1.11)
Installing yyyyyy (2.2.22)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There are 9 dependencies from the Podfile and 11 total pods installed.
無事に成功しました。

急いでいる時程、こういう類のエラーが出ると焦ってしまいますが、
急がば回れで対応すると、案外あっさり解決するものです。


, ,

2017年2月4日土曜日

PHPで Fatal error: Uncaught exception 'ErrorException' with message date() が発生した場合の対処方法

オフィス狛 技術部です。

以前、別の記事で、PHPのインストール(バージョンアップ)を説明しましたが、
その流れで発生するエラーについて、対象方法を記載しておきます。

エラーは
Fatal error: Uncaught exception 'ErrorException' with message date()
です。

さらに詳細は、
Fatal error: Uncaught exception 'ErrorException' with message 'date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone.
となります。要は、
「タイムゾーンが指定されていないよ。指定されていないと、システムのタイムゾーン使うけど、それは安全じゃないよ。一応今はUTCが選択されているけど、ちゃんと自分で指定してね」
という事です。
※PHPのインストール直後は、php.ini がデフォルトの状態になっているので、タイムゾーンは未設定となっています。

PHPのタイムゾーンを設定します

いざ、設定しようとする時、いつも思うのが、
「あれ?php.ini ってどこにあったかな?」
という事です。

まずは、php.ini の場所を探し出します。
$ php -i | grep php.ini
Configuration File (php.ini) Path => /opt/remi/php56/root/etc
Loaded Configuration File => /opt/remi/php56/root/etc/php.ini
Loaded Configuration File にあるのが、パスになります。

あとは、php.ini 内の「date.timezone」を変更するだけです。
例えば、日本だったら、「date.timezone = 'Asia/Tokyo'」という感じです。

PHPのインストールをする場合は、ここまでワンセットで行なっておくのが確実です。
(私はいつも忘れてしまいますが。。。)


,

2017年1月21日土曜日

MySQL では、'a' = 0 が True になる。

オフィス狛 技術部です。

MySQL において、文字列と数値の比較は思わぬ結果になってしまう事があるので注意です。

まず、題名にもある通り、

「SELECT 0 = 'a';」はTrueになります。

・なぜこんな事が起きてしまうのか?

MySQLでは、型が違う値同士を比較すると、暗黙的な変換が発生します。
文字列の「1」は、数値の「1」になります。
しかし、数値に変換出来ないもの、例えば「a」という文字は、
変換出来ないので、「0」になります。
(変換は出来ないけど、型を一緒にする為に、無理やり数値にしてくれる)

変換出来ないなら、諦めて欲しいところなのですが、
頑張り屋さんなんですね、MySQLは。

という訳で、色々試して見た結果が以下の通りです。
SELECT 0 = 0;   -- True になる
SELECT 0 = 1;   -- False になる
SELECT 'a' = 'a'; -- True になる
SELECT 'a' = 'c'; -- False になる
SELECT 0 = '0';  -- True になる
SELECT 0 = 'a';  -- True になる
SELECT 1 = 'a';  -- False になる

やっぱり、「SELECT 0 = 'a'; 」が問題になりそうですね。

・他にもあった、頑張り屋さんならではの弊害

先程、「a」という文字列は数値に出来ませんでした。
では「1a」という文字列ではどうでしょう?
・・・・数値にしてくれるのです。そう、MySQLなら。

この場合、「1a」は「1」に変換されます。

こちらも色々試して見ました。
SELECT 1 = '1'; true
SELECT 1 = '1a'; true
SELECT 1 = '1abcd'; true
SELECT 1 = 'abcd1'; false
どうやら、先頭に数値が来ると、その数値に変換して比較するようです。

・でも、別にMySQLは悪くない

むしろ、頑張り屋さんで、良いと思います。

そもそも、SQLで文字列と数値を比較するような事を発生させないのが筋です。
そして、その制御はSQLに値を設定するプログラム側で行うべきだと思います。
(もっと言うと、そんな事が発生する場合、設計を見直した方が良いのかな、とも思います。)

・その他気になったところ

リファレンスマニュアルには、下記の式についても、 結果が異なると記載されています。
SELECT '18015376320243458' = 18015376320243458; true になる
SELECT '18015376320243459' = 18015376320243459; false になる
これを色々な環境で試してみたのですが、
SELECT '18015376320243458' = 18015376320243458; true
SELECT '18015376320243459' = 18015376320243459; true

間違った判定をされる事はありませんでした。

リファレンスをちゃんと読むと、
さらに、文字列から浮動小数点への変換および整数から浮動小数点への変換は、必ずしも同様に発生するとはかぎりません。整数は、CPU によって浮動小数点に変換される可能性があります。一方、文字列は、浮動小数点の乗算を伴う演算で 1 桁ずつ変換されます。 表示される結果はシステムによって異なり、コンピュータのアーキテクチャーやコンパイラのバージョンなどの要因、または最適化レベルの影響を受ける可能性があります。このような問題を回避する方法の 1 つは、値が暗黙的に浮動小数点値に変換されないように、CAST() を使用することです。
「MySQL 5.6 リファレンスマニュアル」『関数と演算子 / 式評価での型変換 / 12.2 式評価での型変換』より。
2017年1月17日 (火) 08:11 UTC
URL: https://dev.mysql.com/doc/refman/5.6/ja/type-conversion.html
なるほど、環境によって異なるのですね。

MySQL において、異なる型の比較は思わぬ結果になってしまう事がある、
という事で、注意しましょう。

2017年1月14日土曜日

AWS EC2 の GitLab へリポジトリを移行する

オフィス狛 技術部です。

GitLab のインストール・設定シリーズ第3回目の今回は、Git リポジトリの移行を行います。
(移行元と移行先はサーバー(EC2インスタンス)が別)

その他の記事は、以下をご覧下さい。
第1回(AWS EC2 に GitLab をインストールする)
第2回(AWS EC2 に GitLab をインストールする(メールの送信テスト編))

では、早速Git リポジトリの移行を始めて行こうと思います。

1)GitLab にリポジトリ(プロジェクト)を作成する。

作成の手順は記載しませんが、前もって移行するリポジトリと同じ名前で、
GitLab プロジェクト(=リポジトリ)を作成しておく必要があります。

ちなみに、GitLab では、ユーザー、またはグループに属する形でプロジェクトが作成されます。
なので弊社の場合、共有リポジトリ用グループ(例:hogehoge-dev)を作成し、
開発者用のプロジェクトは全て hogehoge-dev に属する様に作成しています。

※あくまで置き場所として用意しているだけなので、権限設定に関しては、
別のグループ等を作成し、プロジェクト単位にグループへの権限付与を行なっています。

以降の説明を簡単にする為に、各設定の名称を以下と仮定します。
 ・旧Gitが存在しているサーバー : old.hoge.co.jp
 ・移行したいリポジトリ名 : RipoTest.git
 ・GitLab(新Git)が存在しているサーバー : new.hoge.co.jp
 ・GitLab のグループ : hogehoge-dev
 ・GitLab のプロジェクト名 : RipoTest

2)旧リポジトリをクローンする

続いて旧リポジトリをクローンします。
(今回、作業は、ローカル(Mac)側のターミナルで行なっています。)
sshで接続するので、旧サーバー側の鍵が必要になります。
鍵は、Macだと「/Users/mac_user/.ssh」に格納します。
※[mac_user]は、現在Macにログインしているユーザーです。

そして、コマンドを簡略化させる為に、同じディレクリに「config」というファイルを作成し、
Host config_name_1
  User connect_user
  HostName old.hoge.co.jp
  IdentityFile ~/.ssh/old_hoge_key_name
上記の内容を記述します。

 Host : コマンドに含める簡略名になります。 
 User : 接続するサーバーのユーザー
 HostName : 接続するサーバー
 IdentityFile : 使用する鍵の場所

これで準備が出来たので、早速クローンします。
$ git clone --mirror ssh://config_name_1/RipoTest.git
「mirror」を付ける事によって、履歴を含めたリポジトリのバックアップを取る感じでクローン出来ます。

3)GitLab(新Git)へプッシュする

続いて、先程クローンしたリポジトリをGitLab(新Git)へプッシュします。
こちらも先程と同じ様に、「config」を設定します。
Host config_name_2
  User git
  HostName new.hoge.co.jp
  IdentityFile ~/.ssh/new_hoge_key_name
※GitLabをインストールした時に、「git」ユーザーが作成されています。

これで準備が出来たので、早速プッシュします。
git push --mirror ssh://config_name_2/hogehoge-dev/RipoTest.git
「mirror」を付けること、そして、GitLab のグループ名をリポジトリ名の前に付ける事を忘れずに。

これで大抵のリポジトリは問題無くプッシュ出来るのですが、リポジトリの容量が大きいと・・・
Counting objects: 7400, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6246/6246), done.
Writing objects: 100% (7400/7400), 558.97 MiB | 6.24 MiB/s, done.
Total 7400 (delta 2794), reused 3512 (delta 452)
remote: fatal: Out of memory, malloc failed (tried to allocate 228733096 bytes)
error: unpack failed: index-pack abnormal exit
To ssh://config_name_2/hogehoge-dev/RipoTest.git
 ! [remote rejected] master -> master (unpacker error)
error: failed to push some refs to 'ssh://config_name_2/hogehoge-dev/RipoTest.git'
と、エラーになる事があります。
これ、中々解決出来ず、ちょっとハマったのですが、
結論から言うと、プッシュ先(ここで言うとGitLabがインストールしてある「new.hoge.co.jp」)のメモリを増やす事で解決しました。(*1

*1) プッシュ先のEC2 インスタンスを「t2.medium」から「t2.large」に一時的に変更しました。
弊社の場合、プッシュが全て終わったら、「t2.medium」へ戻しています。
「t2.large」は料金が高いので・・・。


これでリポジトリの移行が完了です。


, , ,

2017年1月8日日曜日

PHPで Fatal error: Arrays are not allowed as constants が発生した場合の対処方法

オフィス狛 技術部です。

弊社は Codeigniter を使ってWebシステムを作る事が多いのですが、
開発(ローカル)環境で作成したプロジェクトをLinuxサーバへ配置し、
いざURLに接続・・・と言う時に、

Fatal error: Arrays are not allowed as constants

が発生する事が多いです。

原因も分かっているので、毎回同じ対応するのですが、
とりあえず記録として残しておこうと思います。

まず、エラーの内容ですが、直訳でも通じる程シンプルで、
「配列は定数として使えませんよ」と言う事です。

配列を定数として使えるのは PHP 5.6 からで、
その前のバージョンでは使用出来ません。

Linux系のサーバーだと、デフォルトが PHP 5.5 というのが多くて、
毎回、サーバーに配置してから気付く、という感じです。

対処方法としては、PHPをバージョンアップさせるか、
それが無理なら、配列を定数として使う事を諦めるしかありません。

今回は、PHP のバージョンアップの手順を備忘録も兼ねて、残しておこうと思います。
なお、以降の手順は CetnOS の場合を想定しています。

1)現在のPHPのバージョンを確認する

何はともあれ、まずは現在のPHPのバージョンを確認します。
$ php -v
PHP 5.5.38 (cli) (built: Nov  9 2016 17:25:01) 
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies
やはりバージョンは5.5 です。

2)異なるバージョンのPHPをインストールする

次は、現状のPHPとは異なるバージョンのPHPをインストールします。
今回は、PHP 5.6 をインストールします。
$ yum -y --enablerepo=epel,remi,remi-php56 install php56

これでインストールは出来たのですが、PATHは通っていない状態なので、下記を実行します。
$ source /opt/remi/php56/enable
ちなみに、「/opt/remi/php56/enable」の中身は、こんな感じです。
export PATH=/opt/remi/php56/root/usr/bin:/opt/remi/php56/root/usr/sbin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/opt/remi/php56/root/usr/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
export MANPATH=/opt/remi/php56/root/usr/share/man:${MANPATH}

次はphpコマンドのリンク先を変えていきます。
現状のリンク先を確認すると、
$ ls -lrt /usr/bin/php
lrwxrwxrwx. 1 root root 14 11月 15 21:42 /usr/bin/php -> /usr/bin/php55
現状はPHP 5.5にリンクされている事が分かります。
では、一旦リンクを外して、PHP 5.6へリンクするように変更します。
$ unlink php
$ sudo ln -s /usr/bin/php56 /usr/bin/php

正しく変更されているか確認します。
$ ls -lrt /usr/bin/php
lrwxrwxrwx. 1 root root 14 12月  3 12:35 /usr/bin/php -> /usr/bin/php56
$ php -v
PHP 5.6.28 (cli) (built: Nov  9 2016 06:57:19) 
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies

これで、Fatal error: Arrays are not allowed as constants が発生しなくなるはずです。


,