うまとま君の技術めも

2015年新卒入社した社畜の勉強内容などなど

Chef-Zero 入門 (Local Mode)

概要

Chef-Client/Server構成で最近開発を行っているのですが、
Cookbook等を開発している時に毎回Serverにアップロードしたりするのは面倒くさいです。。。

そこで、Chef Zeroを使用すればローカル(Chef-Client側)のみで完結するChef環境が作れるらしいので試してみたいと思います。

環境

What is Chef Zero?

https://www.chef.io/blog/2014/06/24/from-solo-to-zero-migrating-to-chef-client-local-mode/

Chef Zero is a full, in-memory, fast-start Chef server intended for development purposes; it persists no data to disk, nor does it have any authentication or authorization. Later, Chef Zero was rolled into Chef Client 11.8.0, thereby giving us Chef Client local mode, which you run with the --local-mode parameter to chef-client.

メモリ上で起動する簡易的なChef-Serverみたいなものらしい

Chef Zeroを試してみる

※ 下記コマンドは全てCentOS上で行っています。

まずは、Chef-DKをインストールします。
方法はいくつかあると思いますが、今回はwgetrpmを取ってきてインストールしたいと思います。

// Install chef-dk
$ wget https://packages.chef.io/stable/el/7/chefdk-0.14.25-1.el7.x86_64.rpm
$ sudo rpm -Uvh chefdk-0.14.25-1.el7.x86_64.rpm

続いて、Chef Repositoryを生成し、
Cookbook・Environment・Roleをそれぞれ作っていきます。

// Create chef-repo
$ chef generate repo chef-repo && cd chef-repo

// Create Cookbook
$ knife cookbook create -o ./cookbooks/ sample
$ vi cookbooks/sample/recipes/default.rb
$ cat cookbooks/sample/recipes/default.rb
file '/tmp/chef-sample.txt' do
  content node[:sample][:echo]
end

// Create env file
$ vi environments/dev.json
$ cat environments/dev.json
{
    "name": "dev",
    "description": "This is dev environment defined as JSON",
    "chef_type": "environment",
    "json_class": "Chef::Environment",
    "default_attributes": {
        "sample": {
            "echo": "HelloWorld!!"
        }
    },
    "override_attributes": {
    },
    "cookbook_versions": {
        "sample": "= 0.1.0"
    }
}

// Create role file
$ vi roles/app.json
$ cat roles/app.json
{
    "name": "app",
    "description": "This is app role defined as JSON",
    "chef_type": "role",
    "json_class": "Chef::Role",
    "default_attributes": {
    },
    "override_attributes": {
    },
    "run_list": [
        "recipe[sample]"
    ]
}

ここで、一旦local-modeでchef-clientコマンドを実行します。
これにより、chef-repoのnodesディレクトリ内にjsonファイルが生成されます。

// Setup node (create nodes/<hostname>.json file)
$ sudo chef-client -z

node情報を登録することができたので、nodeに対してrun_list・envを設定します。
今回はrun_list: role[app]、env: devとします。

// Add run_list env
$ sudo knife node run_list set localhost role[app] -z
$ sudo knife node environment set localhost dev -z
$ knife node show localhost -z
Node Name:   localhost
Environment: dev
FQDN:        localhost
IP:          192.168.0.5
Run List:    role[app]
Roles:
Recipes:
Platform:    centos 7.2.1511
Tags:

これで、local-modeで実行するための準備が整ったので
-z (--local-mode)オプションを付けて、chef-clientコマンドを実行します。

// Deploy
$ sudo chef-client -z
Starting Chef Client, version 12.10.24
resolving cookbooks for run list: ["sample"]
Synchronizing Cookbooks:
  - sample (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: sample::default
  * file[/tmp/chef-sample.txt] action create
    - create new file /tmp/chef-sample.txt
    - update content in file /tmp/chef-sample.txt from none to f4e9ad
    --- /tmp/chef-sample.txt    2016-06-05 00:09:29.390000000 +0900
    +++ /tmp/.chef-chef-sample.txt20160605-13608-14dhopg   2016-06-05 00:09:29.390000000 +0900
    @@ -1 +1,2 @@
    +HelloWorld!!
    - restore selinux security context

Running handlers:
Running handlers complete
Chef Client finished, 1/1 resources updated in 01 seconds

無事、意図したtmpファイルを生成することができました。

// Check file
$ cat /tmp/chef-sample.txt
HelloWorld!!

まとめ

今まで、Cookbookを開発するときの良い方法がわからず、開発中でもChef-Serverに対してCookbookをアップロードしたりしていました。。。
Chef-Zeroであれば、いちいちServerに対してアップロードしなくても動作確認することができるようなので、今後はこちらを開発に使っていきたいと思います。

sequelizeでidに0が入ってしまう

sequelizeでidに0が入ってしまう

環境

概要

SequelizeでdialectとしてMySQLをした際に、 AUTO_INCREMENTカラムを含むテーブルに対してINSERTすると0が入ってしまう可能性がある。

Sequelize

SequelizeはNode.js用のORMである。

まず、下記のようなテーブルがあるとする

CREATE TABLE `Users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

ここで、sequelizeで定義したModelからnameカラムのみを指定してinsertすると 下記のようなSQLが発行される

INSERT INTO `Users` (`id`,`name`) VALUES (DEFAULT,'sequelize');

この時、設定によってはUsersに追加されるレコードは下記のようなものになる場合がある

select * from Users;
+----+-----------+
| id | name      |
+----+-----------+
|  0 | sequelize |
+----+-----------+

NO_AUTO_VALUE_ON_ZERO

MySQLではsql_modeという設定があります。

https://dev.mysql.com/doc/refman/5.6/ja/faqs-sql-modes.html

  1. サーバー SQL モードとは何ですか。
  2. サーバー SQL モードは、MySQL でサポートされる SQL 構文、および実行されるデータ妥当性チェックの種類を定義します。

sql_modeの1つにNO_AUTO_VALUE_ON_ZEROがあり、 これを設定すると、INSERT時にAUTO_INCREMENTカラムに対して0を指定しても自動採番されなくなります。

NO_AUTO_VALUE_ON_ZERO は AUTO_INCREMENT カラムの処理に影響します。通常は、NULL または 0 をカラムに挿入することによって、カラムの次のシーケンス番号を生成します。NO_AUTO_VALUE_ON_ZERO は 0 のこの動作を抑制するため、NULL のみが次のシーケンス番号を生成します。

https://dev.mysql.com/doc/refman/5.6/ja/sql-mode.html

また、MySQLでは数値型の初期値は0なので、DEFAULTは0にります。 https://dev.mysql.com/doc/refman/5.6/ja/data-type-defaults.html

なので、NO_AUTO_VALUE_ON_ZEROを設定している状態でidに対してDEFAULTを指定すると 必ずidに0が入るようになってしまいます。

対応

下記SQLsql_modeを確認、設定することができます。 状況に応じて、適切なsql_modeを設定すれば良さそう?

SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;

SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';

まとめ

Sequelizeを使うときは、sql_modeの設定を事前に確認しましょう。 MySQLに対しての知識が全然ないので、地味にハマりました。。。

RMagickでフォントが使用できない、unable to read font `(null)'

環境

unable to read font `(null)'

rmagickでテキストを表示しようと思ったら何故かunable to read font(null)'`とかいうエラーが。。。

rmagickで使用できるフォントを見てみると、使用可能なフォントが1つもない。。。

require 'RMagick'

Magick.fonts
=> []

設定ファイルを確認

設定ファイルを確認してみると、--with-gs-font-dir=/usr/local/share/ghostscript/fontsという部分がある。 どうやら、ここにあるフォントを読み取って使用しているらしいが、そんなディレクトリは存在しない。。。

$ cat /usr/local/lib/ImageMagick/config-Q16/configure.xml

ghostscript

無いのであればinstallしてみる

$ brew install ghostscript

無事フォントが読み込まれました

require 'RMagick'

Magick.fonts
=> [#<struct Magick::Font name="AvantGarde-Book", ...]

.btn-groupにtooltipを使うとデザインがズレる

環境

.btn-groupにtooltipを使うとデザインがズレる

.btn-group内のbuttontooltipを使用すると、 tooltipが発動した時に1px程度ズレが生じてしまいます。

f:id:umatomakun:20150321171659g:plain

<div class="btn-group" role="group">
    <button
        type="button"
        class="btn btn-default"
        data-toggle="tooltip"
        title="Tooltip">
        Left
    </button>
    <button type="button" class="btn btn-default">Middle</button>
    <button type="button" class="btn btn-default">Right</button>
</div>

何故ズレが生じているのか?

Bootstrapのソースコードを見てみると、隣接している.btnmargin-left: -1px;が指定されているようです。

github.com

.btn-group {
  .btn + .btn,
  .btn + .btn-group,
  .btn-group + .btn,
  .btn-group + .btn-group {
    margin-left: -1px;
  }
}

しかし、tooltipを使用するとtooltipの対象となっている.btnの後にDOMが追加されてしまい、.btn同士が隣接している状態ではなくなってしまいます。 そうすると、上記cssが適応されないのでレイアウトがズレてしまっているようです。

f:id:umatomakun:20150321203149p:plain

ズレが生じないようにする

対処法としてはdata-containerbody等に設定してあげればOKです。

<div class="btn-group" role="group">
    <button
        type="button"
        class="btn btn-default"
        data-toggle="tooltip"
        data-container="body"
        title="Tooltip">
        Left
    </button>
    <button type="button" class="btn btn-default">Middle</button>
    <button type="button" class="btn btn-default">Right</button>
</div>

参考

Bootstrapの公式サイトにもdata-containerを指定するよう載っていますね。

getbootstrap.com

When using tooltips on elements within a .btn-group or an .input-group, you'll have to specify the option container: 'body' (documented below) to avoid unwanted side effects (such as the element growing wider and/or losing its rounded corners when the tooltip is triggered).

Drawableに設置したpngファイルのオリジナルサイズを取得する

AndroidにてDrawableに設置したpngファイルのサイズを取得しようとしたら、ちょっとはまったのでメモ。

前提

drawableに 50 * 50 px の画像ファイルを設置。
res/drawable/sample.png

画像サイズを取得

リソースIDからBitmapを生成して#getWidth, #getHeightでサイズを取得したら、 実際のサイズとは異なる値が取得される。。。

Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.sample);
int w = b.getWidth();
int h = b.getHeight();
// w => 150; h => 150;

そんな時はBitmapFactory.Optionsから値を取得してあげれば、 オリジナルの画像サイズが取得出来ました( ´ー`)フゥー...

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.sample, options);
int w = options.outWidth;
int h = options.outHeight;
// w => 50; h => 50;

参考

http://stackoverflow.com/questions/8855036/incorrect-image-dimensions-in-android-when-using-bitmap

Enumerable#map and Array#flatten vs Enumerable#flat_map

目的

  • Enumerable#map, Array#flatten と Enumerable#flat_map でどちらがパフォーマンスに優れているかを調べる。

環境

Enumerable#flat_map

flat_map (Enumerable) - APIdock : http://apidock.com/ruby/Enumerable/flat_map

ベンチマーク

require 'benchmark'

n = 1000
array = Array.new(100) { |i| Array.new(i) { |j| j } }

Benchmark::CAPTION
Benchmark.bm do |x|
  x.report('flat_map') { n.times { array.flat_map { |a| a } } }
  x.report('flat map') { n.times { array.map { |a| a }.flatten(1) } }
end
       user     system      total        real
flat_map  0.030000   0.010000   0.040000 (  0.036725)
flat map  0.260000   0.020000   0.280000 (  0.271837)

まとめ

map , flattenではなくflat_mapを使ったほうが速くなっていますね。

ただ、flat_mapでは1段階までしか平坦化されないで使用する際には注意が必要かもしれないです。

irb(main):055:0> [[1, 2], [[3, 4, 5]]].flat_map { |x| x }
=> [1, 2, [3, 4, 5]]

Railsで独自クラスにバリデーションを実装してみる

環境

独自クラスにValidationを実装

独自クラスでRailsのValidationを実装したい場合はActiveModel::Validationsをincludeしてあげればよいらしい。

class Hoge
  include ActiveModel::Validations

  validates :hoge_id, presence: true
  
  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value)
    end
  end
end

これで、大丈夫かと思ったがNoMethodErrorが出てしまった。

[1] pry(main)> Hoge.new(hoge_id: 100)
NoMethodError: undefined method `hoge_id=' for #<Hoge:0x007fc71ce47bf0>
from /path/to/hoge.rb:8:in `block in initialize'

なので、attr_accessor :hoge_idを指定してあげればエラーは消えた.

class Hoge
  include ActiveModel::Validations

  attr_accessor :hoge_id # 追加

  validates :hoge_id, presence: true
  
  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value)
    end
  end
end

これで、ひとまず独自クラスでもバリデーションを行うことが出来るようになりました。

[5] pry(main)> hoge = Hoge.new(hoge_id: nil)
[6] pry(main)> hoge.valid?
=> false
[7] pry(main)> hoge.errors.full_messages
=> ["Hogeを入力してください。"]

参考