Thanks Driven Life

日々是感謝

terraform plan の実行結果で、属性値が変更になる行に色付けする sed

TL;DR

readonly escape_ansi=$(printf '\033')

sed -e '/".*" => ".*"$/!b' \
    -e '/^.*: *"\(.*\)" => "\1"$/!s/.*/'"$escape_ansi"'[31m&'"$escape_ansi"'[m/'

f:id:gongoZ:20160702110516p:plain

経緯

毎晩暑い日が続く日本、AWS の各種リソース管理を Terraform で行っている皆様におかれましては、 日々の業務において terraform plan をよくお使いになられていることと存じます*1

そんな中、例えばこのような実行結果が表示されたとします:

..
.. (message)
..

+ aws_security_group.app
    ingress.#:                   "" => "1"
    ingress.0.cidr_blocks.#:     "" => "1"
    ingress.0.cidr_blocks.0:     "" => "192.0.2.0/24"
    ingress.0.from_port:         "" => "80"
    ingress.0.protocol:          "" => "tcp"
    ingress.0.to_port:           "" => "80"

-/+ aws_security_group.db
    ingress.#:                   "2" => "2"
    ingress.0.cidr_blocks.#:     "1" => "1"
    ingress.0.cidr_blocks.0:     "198.51.100.0/24" => "198.51.100.0/24"
    ingress.0.from_port:         "3306" => "3306"
    ingress.0.protocol:          "6" => "tcp"
    ingress.0.to_port:           "3306" => "3306"
    ingress.1.cidr_blocks.#:     "" => "1"
    ingress.1.cidr_blocks.0:     "" => "203.0.113.0/24"
    ingress.1.from_port:         "" => "5432"
    ingress.1.protocol:          "" => "tcp"
    ingress.1.to_port:           "" => "5432"

-/+ aws_security_group.mail
    ingress.#:                   "2" => "1"
    ingress.0.cidr_blocks.#:     "1" => "1"
    ingress.0.cidr_blocks.0:     "198.51.100.0/24" => "198.51.100.0/24"
    ingress.0.from_port:         "587" => "587"
    ingress.0.protocol:          "6" => "tcp"
    ingress.0.to_port:           "587" => "587"
    ingress.1.cidr_blocks.#:     "1" => ""
    ingress.1.cidr_blocks.0:     "198.51.100.0/24" => ""
    ingress.1.from_port:         "995" => ""
    ingress.1.protocol:          "6" => ""
    ingress.1.to_port:           "995" => ""

Apply complete! Resources: 1 added, 2 changed, 0 destroyed.

(実行結果はてきとうです)

各セキュリティーグループのルール数が2、3個なのでまだまだ見えますが、例えばルール数が20個とかになるとかなりの行数が目に入ります。その中で どの行(属性)が変更なのかぱっと見、わかりづらい と思うわけです。思いました。

まあそんなわけで、属性値に変更のある場所がわかりやすく見えればいいな、ということで、いくつか方法はあると思いますが、今回は sed でやってみました。

sed

readonly escape_ansi=$(printf '\033')

sed -e '/".*" => ".*"$/!b' \
    -e '/^.*: *"\(.*\)" => "\1"$/!s/.*/'"$escape_ansi"'[31m&'"$escape_ansi"'[m/'
  1. "xx" => "yy" という文字列(xxとyyは任意)を 含まない 行はスキップして次に進む
  2. "xx" => "xx" という条件に一致しない(つまり左と右の "" 内の値が 同じではない )場合 に、赤で色付けする

こんな感じでできました。パターンは単純なんですが terraform plan の実行結果に対してだけ使うのであれば充分かなと思います。

ついでにこいつをスクリプトに落とすと、雰囲気こんな感じ

#!/bin/sh

readonly escape_ansi=$(printf '\033')
readonly program_name=$0

if [ -p /dev/stdin ] ; then
    cat -
else
    if [ ! $# -ge 1 ] ; then
        echo "$0: [ERROR] You must specify file."
        exit 1;
    fi

    if [ ! -f "$1" ] ; then
        echo "$0: [ERROR] \"$1\" No such file."
        exit 1;
    fi

    cat "$1"
fi | sed -e '/".*" => ".*"$/!b' \
         -e '/^.*: *"\(.*\)" => "\1"$/!s/.*/'"$escape_ansi"'[31m&'"$escape_ansi"'[m/'

てきとうに tpdiff とか名前で保存しておいて

$ terraform plan | tpdiff

こんな感じで、冒頭の画像のように表示されます。多分。

ひとまず

あたりでは動いているようです。

まとめ

sed 難しい

おまけ

当初、sed の色付けする部分を

-e $'/^.*: *"\\(.*\\)" => "\\1"$/!s/.*/\e[31m&\e[m/'

みたいな感じでやってたんですが、 POSIX 準拠してるかってチェックしてくれる shellcheck を使うと

fi | sed -e '/".*" => ".*"$/!b'
         -e $'/^.*: *"\\(.*\\)" => "\\1"$/!s/.*/\e[31m&\e[m/'
            ^-- SC2039: In POSIX sh, $'..' is undefined.

ってな感じで怒られたので、printf 使うようにしました。

*1:手元実行なんかせず、Atlasにお任せしてる人が多いのかもしれない

「Methods & Tools Summer 2016 Magazine」に Turnip について寄稿しました

A free software development magazine であるところの Methods & Tools に 「Turnip - Gherkin extension for RSpec」というタイトルで寄稿しました。

Methods & Tools - Summer 2016

書いたこと

  1. テストやってますか。受け入れテストどうですか。
  2. Cucumber が人気だよね
  3. だけど人によっては Cucumber のこういう部分がちょっと気になる人いるよね
  4. そこらへんを解決しようとしてるのが Turnip です
  5. Turnip 導入編
  6. Turnip のこういう機能が、3 で挙げた点を解決してるのだー
  7. まとめ

目新しいことは何もなく、Turnip の誕生理由とか Cucumber との軽い比較ぐらいですね。

経緯

Methods & Tools の編集者(なのかな)の Franco さんから「Turnip の紹介記事とか書いてみませんか」みたいなメールが来ました。 正直なところ Methods & Tools の存在をこの時知りました。1999年から続いてる老舗なんですね…

ちなみに Turnip 作者である Jonas さんに「この件どうしましょうかー」って聞いたところ

jonas「書いていいよ」
gongo「お、おう」

という感じで。まあせっかくの機会だしなってことで、私がメインで、英語のおかしい点を jonas さんに見てもらう、という体制で始めました。

できた

この後 jonas さんにめっちゃくちゃ添削してもらった

終わり

今回はただの紹介記事だったのでそれほどの量でもなく、英語も難しい言い回しとかは無かったのですが、 それでもなかなか書けず時間がかかってしまいました。英語すごい。

そんなわけでよろしくお願い致します。

「1時間後に OSX をスリープする」をセットするコマンド

$ sudo pmset schedule sleep "$(date -v +'1H' +'%D %T')"

OSX 10.11 El Capitan および OSX 10.10 Yosemite で動作確認しています。おそらくそれ以下のバージョンでも動くやつは動くと思います。多分。

経緯

私は就寝する時に Mac mini で音楽を鳴らしながら床に就くのですが、つけっぱなしなのもあれなので、毎晩寝る直前に

  1. システム環境設定
  2. 省エネルギー
  3. スケジュール

を経て*1、「今は0時だから、1時にスリープするようにセットして…と…」みたいな感じで設定しています。 だいたい寝る時間は一定なのですが、夜更かしするなどしてスリープ時刻を過ぎてから寝ることもしばしばなので そういう時は上記の設定を毎回行っています。

正直めんどいのでコマンドラインでぱぱっとできないかなと調べてみました。

pmset

遅ればせながら pmset というコマンドの存在を知りました。

スリープモードの変更(hibernateとか)等もできるらしいのですが、今回使うのは pmset schedule です。

pmset schedule

schedule サブコマンドでは引数として

  • sleep
  • wake
  • poweron
  • shutdown
  • wakeorpoweron

これらの type と時刻を受けとります。例えば

$ sudo pmset shceudle wake "07/04/16 20:00:00"

とすれば「2016年7月4日の午後8時 にマシンをスリープから復帰させろ(wake)」になります。便利。

「1時間後」の時刻を取得する

おなじみの date コマンドを使います。

pmset schedule が許容する時刻のフォーマットは MM/dd/yy HH:mm:ss なので

$ date -v +1H +'%D %T'

こうなります *2

最終的に

組合せると、冒頭の

$ sudo pmset schedule sleep "$(date -v +'1H' +'%D %T')"

になります。お疲れ様でした。

余談ですが

しっかり登録されてるかを確認しましょう。

$ date
Wed Apr 20 09:51:24 JST 2016

$ sudo pmset schedule sleep "$(date -v +'1H' +'%D %T')"

$ pmset -g sched
Scheduled power events:
 [0]  sleep at 04/20/16 10:51:27

OK

まとめ

眠い

*1:https://support.apple.com/kb/PH18583

*2:OSX標準の date は BSD date なので、時刻計算のオプションが GNU date と違う。GNU date だと -d '1 hours' みたいな感じ

Turnip 2.1.0 リリースしました

Release Version 2.1.0 · jnicklas/turnip

Ruby のサポートバージョンポリシー変更

先月24日に Ruby 2.0 が EOL を迎えたということで、それに合わせて Turnip も今後は Ruby 2.1 以上をサポートすることにしました。 Ruby 2.0 お疲れ様でした。Ruby 1.9.3 や Ruby 2.0 で世界はいろいろ動いた気がします。

See: Ruby 2.0.0 および Ruby 2.1 の今後について

カスタムプレースホルダーでデフォルトの値を返す DSL の追加

@ さんからの Pull Request を経て最終的にこうなりました。

placeholder :user_name do
  match /admin: (.*)/ do |user_name|
    User.find_by!(name: user_name, role: :admin)
  end

  default do |user_name|
    User.find_by!(name: user_name)
  end
end

こんなプレースホルダが定義されている時

When admin: gongo がログイン
When wataru がログイン

みたいなステップを書いていると、前者の step ブロックには name = gongo and role = admin を満たす User インスタンスが、 後者には name = wataru を満たす User インスタンスが渡されるという感じです。

まとめ

Ruby 2.0 の EOL と新 DSL の追加ということで 2.0.x から 2.1.0 に上げました。よろしくお願い致します。

Ruby 2.x 環境で magic comment 無しファイルを開くと flycheck で "invalid multibyte char (us-ascii)" と言われる場合

結論から言うと

(setenv "LC_ALL" "ja_JP.UTF-8")

;; or

(setenv "LANG" "ja_JP.UTF-8")

を試しましょう

経緯

こんなファイルのことです。

p 'あいうえお'

f:id:gongoZ:20160210091618p:plain

メッセージ自体はお馴染のものなので

といった情報がググるとすぐ出てきます。確かに ↑ のファイルに # coding: utf-8 を追加したらメッセージは出なくなりました。

ですが私が使っているマシンにはもはや Ruby 1.9 は入っていないので、このメッセージが出るのがおかしい。ってことで謎は深まっておりました。

原因

そんなときに @ さんより

といったアドバイスを頂いたので

(setenv "LC_ALL" "ja_JP.UTF-8")

を実行してみると

f:id:gongoZ:20160210092451g:plain

消えました。やったぜ。@ さんありがとうございました!

まとめ

以前から https://github.com/purcell/exec-path-from-shell は使っていて GOPATH とか RBENV_ROOT は取り込むようにしていましたが、 LC_ALL とかのことは忘れていました。要注意。

「Serverspec で期待値を直書きしてる部分、Puppet の hiera から持ってこれるけどどうする?」

Puppet や Serverspec に限らず、Chef でも Itamae でも awspec でも自前スクリプトでも何でもいい話なんですが。 つまり テストコードに書く期待値を、構成管理ツールで使っているパラメータファイルから取ってきたほうがいいのか という話題。

結論から言うと「何をテストしたいのか」によって Yes/No が変わるんじゃないかな、と思います。

前置き: 例えばこんなこと

hiera でこう書いてたとして

---

packages:
  - emacs
  - vim

manifest でこう書いてたとすると

$packages = hiera('packages')
package { $packages:
  ensure => latest
}

Serverspec ではこう書くと思います

describe package('vim') do
  it { should be_installed }
end

describe package('emacs') do
  it { should be_installed }
end

ある日、こんな要望がありました

  1. PHP 追加したいから hiera に書いといて
  2. emacs 必要無くなったから hiera から消しといて
 packages:
-  - emacs
   - vim
+  - php-common

このとき、テストコードでは describe package('xxx') の部分を一つずつ直していくことになるでしょう。 数はまだ少ないですが、もしパッケージ数が5個とか10個とかになるとどうでしょうか。 また、パッケージだけならともかく、service だったり iptables だったり、その他細々としたものを書き換える度に

「よっしゃ、manifest 書いてデプロイ完了したぜっってテスト落ちてる…あーはいはい、テストコードの部分を更新し忘れてましたね…」

となることはないでしょうか*1。 その時にアナタはこう思うでしょうか。


「テストコードも hiera を参照してくれれば変更点1箇所で済むのに」


すると、アナタはこんな感じでがんばるかもしれません

hiera = Hiera.new(config: '/path/to/hiera.yaml')
hiera.config[:yaml][:datadir] = '/path/to/hiera/hiera_data'
packages = hiera.lookup('packages', nil, {}) # ['vim', 'php5-common']

packages.each do |pkg|
  describe package(pkg) do
    it { should be_installed }
  end
end

こうすることで

  1. パラメータの変更を hiera に反映する
  2. puppet apply
  3. rspec

という形で サーバの仕様変更におけるパラメータ編集箇所 が1箇所に集約できるメリットが生まれました

本題: これでいいのかどうか

これまでの流れからすると、おそらく Puppet でサーバを構築しつつ Serverspec でテストしているこの人は

さきほど修正した hiera が、ちゃんとサーバに apply されているかどうか

というのを一番チェックしたいと考えていると思います。

f:id:gongoZ:20160115162912p:plain

これ自体は特に問題ないと思っていて、ちゃんとテスト流してて素晴しいですよねみたいな未来です。


ですがこれはあくまで視点の一つで、おそらくもう一つ、下図のような見方をすることもあると考えています。

f:id:gongoZ:20160115162916p:plain

つまり直前にテストしたかどうか、とかは関係なく、純粋に 現時点でのサーバが期待した状態(つまり仕様)を保っているか を中心とするテストです。

この場合、逆に hiera の値をテストコードに使うのはよくなくて

  1. hiera で php を追加するつもりが ruby を追加してしまった
  2. 気づかずに apply
  3. rspec
  4. 本当は php がインストールされていてほしいが、hiera 上は ruby が正義なので PASS

ここもレビューをしっかりしていれば防げるかもしれません。しかし、往々にして人は見逃してしまうものなので、ありえない未来じゃないなと思っています。

まとめ

私が業務で触っている部分では

  • Puppet
  • Terraform

に対して

  • Serverspec
  • infrataster
  • awspec

のテストを用意していて、それぞれのディレクトリも似たような構成にしています。 そのため、hiera ファイルをそのまま使えちゃうので、どうしても「テストコードにも書くのめんどいわー」みたいなことが、無くも無いです。

最終的にはどちらが良い悪い、ということではなくて「何をテストしたいか」っていう当たり前のことを忘れないでいてくれれば10年後の8月また元気で出会えるんじゃないかなと思います。

*1:マニフェストとテストコードをしっかり同時にレビューしてると、こういうのは無さそうですが…

Turnip 2.0.2, turnip_formatter 0.4.0, gnawrnip 0.4.0 リリースしました

新機能追加したわけじゃないです

Turnip 2.0.2

これまで gherkin3 という名前で開発が進んでいた gherkin の version3 が gherkin に rename されました。

https://github.com/cucumber/gherkin/commit/d47add1a5e1a835af8c5dd9f55f7fe7a4916c7dd

これにより、例えば Turnip 2.0.1 の gemspec のように

s.add_runtime_dependency "gherkin", ">= 2.5"

こう書いていると gherkin2 と互換性のない gherkin3 がインストールされるようになった というわけです。 issue で教えてもらいました。感謝 https://github.com/jnicklas/turnip/issues/169

turnip_formatter, gnawrnip 0.4.0

turnip_formatter は Turnip 2.0.2 に、gnawrnipturnip_formatter 0.4.0 に それぞれ追従する形で gemspec 直したりテスト対象の RSpec のバージョン直したりしてました。 たぶん動くと思います。

Gherkin3 について

将来的には Gherkin3 への対応をしようかなと考えてたりします。

Use Gherkin3 · Issue #171 · jnicklas/turnip

いつになるかはわかりませんが。