DRYな備忘録

Don't Repeat Yourself.

docker-compose buildでbundle installがCould not fetch specs from rubygems.orgとなる

問題

Mac上でdocker-machineでVMを立て、そこに向けてdocker-composeを華麗に決める

% docker-compose build
# 略
Step 5 : RUN bundle install
 ---> Running in e8c4e026277a
# 略
Fetching source index from https://rubygems.org/
Could not fetch specs from https://rubygems.org/
ERROR: Service 'hisyotan' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 17

調査

qiita.com

これが似てる

解決

% docker-machine restart oppai

したら治った

RedshiftのCOPYコマンドが失敗する

問題

docs.aws.amazon.com

S3からRedshiftにレコードをコピー(insert)できるコマンドCOPYが便利なので

COPY users
FROM 's3://my_bucket/backup.users.csv'
CREDENTIALS 'aws_access_key_id=xxx以下略'
CSV

とかすると

ERROR:  Load into table 'users' failed.  Check 'stl_load_errors' system table for details.

と叱られる

調査

言われた通り、

psql psql -U ${USER} -h ${HOST} -p ${PORT} ${DB_NAME}

で入り、stl_load_errorsというシステムテーブルがあるというので、ちょっと確認

STL_LOAD_ERRORS - Amazon Redshift

my_db=# \d stl_load_errors;
            Table "pg_catalog.stl_load_errors"
     Column      |            Type             | Modifiers
-----------------+-----------------------------+-----------
 userid          | integer                     | not null
 slice           | integer                     | not null
 tbl             | integer                     | not null
 starttime       | timestamp without time zone | not null
 session         | integer                     | not null
 query           | integer                     | not null
 filename        | character(256)              | not null
 line_number     | bigint                      | not null
 colname         | character(127)              | not null
 type            | character(10)               | not null
 col_length      | character(10)               | not null
 position        | integer                     | not null
 raw_line        | character(1024)             | not null
 raw_field_value | character(1024)             | not null
 err_code        | integer                     | not null
 err_reason      | character(100)              | not null

こんな感じで出してみる。(なお、colnameとかerr_reasonとか、固定長なのでTRIMしないと厳しい出力を得る)

SELECT starttime, TRIM(colname), err_code, TRIM(err_reason)
FROM stl_load_errors
ORDER BY starttime DESC LIMIT 2;
         starttime          | btrim  | err_code |                     btrim
----------------------------+--------+----------+------------------------------------------------
 2015-12-22 08:47:17.143206 |  age   |     1207 | Invalid digit, Value 'a', Pos 0, Type: Integer
 2015-12-21 17:15:05.32209  |        |     1202 | Extra column(s) found
(2 rows)

1207 | Invalid digit, Value 'a', Pos 0, Type: Integer

これはロードエラー参照 - Amazon Redshiftによると

期待される 0~9 の範囲外の値がデータに含まれています。

たしかにageカラムはint型だが、aなんて値は入れてない。

csvの内容見ると、

id_str,username,age
xxxx,otiai10,29

となってて、この最初の行のageという文字がひっかかってる気がする。

解決

ためしに、csvにおけるカラム名の行を削除し

xxxx,otiai10,29

というデータを食わせてみると、

INFO:  Load into table 'users' completed, 1 record(s) loaded successfully.
COPY

となった。そうすっと、カラム名と値の対応はどうすんだろうと思ったが、どうやら

my_db=# \d users
         Table "public.users"
   Column   |            Type             | Modifiers
------------+-----------------------------+-----------
 id_str    | character varying(256)      |
 username  | character varying(256)      |
 age       | integer                     |

CRATE TABLEした順、ということなのか?

結論

  • COPYコマンドに食わせるCSVに、列名行(最初の1行目)は不要

追記

my_db=# SELECT starttime, trim(colname), trim(err_reason) FROM stl_load_errors ORDER BY starttime DESC LIMIT 2;
         starttime          |   btrim    |                           btrim
----------------------------+------------+-----------------------------------------------------------
 2016-01-04 03:41:26.350018 | created_at | Invalid timestamp format or value [YYYY-MM-DD HH24:MI:SS]
 2016-01-04 03:26:50.634297 | created_at | Invalid timestamp format or value [YYYY-MM-DD HH24:MI:SS]
(2 rows)

sendResponseが動いてないように見える現象に半年に1回ひっかかってる気がする

background.js

chrome.runtime.onMessage.addEventListener((req, sender, sendRes) => {
    setTimeout(() => {
      // ここまで来てるのに
      sendRes("元気でーす");
    }, 100);
});

contents_script.js

chrome.runtime.sendMessage(null, {msg:"元気ですか?"}, (res) => {
    window.alert("返事:" + res); // ここに来ない
});

原因

stackoverflow.com

chrome.runtime - Google Chrome

sendResponse
Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one onMessage listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).

レスポンスを返したいときはこのコールバック関数を使うこと。引数にはJSONにできるオブジェクトを渡す。onMessageに複数のリスナーつけても、ひとつしか呼ばれないよ。このコールバック関数は、イベントリスナー関数がreturnすると、使えなくなるよ。もしイベントリスナー関数がreturnした後もコールバック関数を使ってレスポンスを返したければ、イベントリスナー関数で true を返して、非同期でこのコールバック関数を使うことを明示してくれ(sendResponseが呼ばれるまでチャンネルを閉じないでおくので)。

そういやそうだったわ。

chrome.runtime.onMessage.addEventListener((req, sender, sendRes) => {
    setTimeout(() => {
      sendRes("元気でーす");
    }, 100);
+    return true;
});

DRYな備忘録として

アメッシュをターミナルに表示して、ついでに雨降ってたらSlackでおしえてくれるところまで、Goでやったことのまとめ

このエントリはGo その2 Advent Calendar 2015 - Qiitaの8日目です。

7日目のS_Shiomtoriさんの記事コマンドラインツールの話でした。

amesh

% go get github.com/otiai10/amesh/amesh
% amesh -g

思ったことや詰まったこと書きます

Goでコマンドラインツールをつくりはじめるとき

なんでわざわざGoでコマンドラインツールつくるか

  • コンパイルが非常に早いので、ほぼスクリプト言語のようにソースファイルをWrite&Runして開発することができるから
  • ロスコンパイルが容易なので、Goのコンパイル環境を要求せず、実行可能ファイルを納品できるから
  • 並行処理を書きやすく、わりとデカめのことをさくっとやってのけたりするから

Goでコマンドラインツールつくるときいつもハマること

  • flagパッケージわりと使いにくい
  • flag.Args、flag.Arg(n)、os.Argsで迷う
  • コマンドfooのとき foo hoge -fugafoo -fuga hoge で挙動がけっこう違ったりする

今回該当するコードはこのへん

なお、Goでコマンドラインツールつくるときは@deeeetさんの記事がめっちゃ参考になります

Goで画像を扱う

Goは標準ライブラリが上から下まで充実している。ここでは、imageパッケージとimage/drawパッケージの紹介。

package main

import (
    "fmt"
    "image"
    "os"

    // ここでimportしたフォーマットのみ
    // image.Decodeでデコードが可能
    _ "image/png"
    // _ "image/gif"
    // _ "image/jpeg"
    // 複数取りうるならすべてimportしておけばよい
)

func main() {
    f, _ := os.Open("ritsu.png")

    // image.Decodeはio.Readerインターフェースを受けるので
    // http.Response.Bodyとかでもいける
    img, format, err := image.Decode(f)

    // 画像フォーマットによらず、image.Imageは
    // カラーモデル ColorModel() と
    // 大きさ Bounds() と
    // 座標における色情報 At(x, y int) を提供するインターフェースを持つ
    fmt.Println(img.Bounds(), format, err)
}

// (0,0)-(256,256) png <nil>

で、image/drawはもっとすごい

みんな大好きアメッシュは、あれ実は3枚の画像から構成されていて、これをターミナルに表示しようと思ったら画像を合体させる必要があった。該当箇所はここ。

大きさが等しい3枚の画像だったので、あまり深く考えずにdraw.Drawに2枚の画像をぶっ込んでるだけ。

Goでターミナルの大きさを取得する

画像をドット絵にしなきゃいけない限り、表示範囲が小さいと意味不明な絵になってしまう。なので、ターミナルの可視領域を取得したい。 そんなことできるのだろうか、と思ってたらあった。

上では画像処理というわりと高レベルなパッケージを提供している傍ら、こちらではsyscallというごくごく低レベルな機能も標準パッケージで提供している。Go言語しゅごい。

で、取得したターミナルのサイズで、画像を分割して、ターミナル上で表示できる限りの近似色を探して表示すればよい。

ritsu

どうせなら雨降ってたらSlackでおしえてくれや

ターミナルに表示するとかしないとかはあんまり関係ないんですが、アメッシュの画像を取得し、画像の色情報をごにょごにょするので、「雨降ってる判定」だけ追加すれば通知できるんではないか、と思ったので作った。

amesh -d

とすれば、3分おきにアメッシュ見に行って、取得できた画像を雨判定する。もし必要な環境変数がそこにあれば、SlackとかTwitterとかで通知してくれるようにした。

touch develop.env
docker-compose up

で立つはずです。

現状では、雨天判定を以下のようにしてる

// DefaultIsRainingFunc ...
// とりあえず全ピクセル舐めで
// ちょっとでも雨のピクセルが全体の30%を越えてたら雨ってことにする
func DefaultIsRainingFunc(ev Event) bool {
    max := ev.Img.Bounds().Max
    var hit, all float64 = 0, float64(max.X) * float64(max.Y)
    for y := 1; y < max.Y-1; y++ {
        for x := 1; x < max.X-1; x++ {
            r, g, b, a := ev.Img.At(x, y).RGBA()
            if r+g+b+a > 100 {
                hit++
            }
        }
    }
    var threshold float64 = 30
    if (hit*100)/all > threshold {
        return true
    }
    return false // 快晴だこれ
}

雨天判定がこれでいいのか、いい塩梅を探したいんだけど検証できてない。

ここ数日、雨がまったく降ってないので。

結論


明日はogataka50氏です

Elasticsearchの2.0にキャッチアップしたいんですが、Dockerで分離したい。Macで

背景

  • みんなだいすきElasticsearchがいつのまにか2.0になってた
  • これを叩いていろいろためしたい
  • だけど生ローカルで動いているElasticsearchがあり、これはカジュアルにAPI変えたくない
  • Dockerイメージあるし、docker-machineでホストごと分離するのがよさそう

f:id:otiai10:20151123222029p:plain

ログ

% docker-machine create es2.0 --driver virtualbox
% docker-machine env es2.0
% eval $(docker-machine env es2.0)
% docker-machine ssh es2.0
# 以下、machine内での作業
docker@es2:~$
docker@es2:~$ docker run -d elasticsearch
23109478735ac87439c07df34a47bca40e3f4c14d688791a552489b8921778ee
docker@es2:~$ curl "http://localhost:9200"
curl: (7) Failed connect to localhost:9200; Connection refused

あらん?

Connection refused

docker@es2:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
23109478735a        elasticsearch       "/docker-entrypoint.s"   About a minute ago   Up About a minute   9200/tcp, 9300/tcp   modest_archimedes

PORTは空いてる気がするけれど、EXPOSEかな?

# さっきのはいったん殺す
docker@es2:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
23109478735a        elasticsearch       "/docker-entrypoint.s"   About a minute ago   Up About a minute   9200/tcp, 9300/tcp   modest_archimedes
docker@es2:~$ docker kill 23109478735a
23109478735a
docker@es2:~$ docker rm 23109478735a
23109478735a
docker@es2:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
docker@es2:~$

PORTをホストマシンのPORTにバインドしてrunする

docker@es2:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
docker@es2:~$ docker run -d -p 9200:9200 elasticsearch
2b03d2ded545008de52b6c18c3a79e9dc475b9b895a101564eb65723b67e4838
docker@es2:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
2b03d2ded545        elasticsearch       "/docker-entrypoint.s"   7 seconds ago       Up 7 seconds        0.0.0.0:9200->9200/tcp, 9300/tcp   tender_fermat
docker@es2:~$ curl "http://localhost:9200?pretty"
{
  "name" : "Spider-Girl",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.0.0",
    "build_hash" : "de54438d6af8f9340d50c5c786151783ce7d6be5",
    "build_timestamp" : "2015-10-22T08:09:48Z",
    "build_snapshot" : false,
    "lucene_version" : "5.2.1"
  },
  "tagline" : "You Know, for Search"
}
docker@es2:~$

無事、docker-machineでホストVMのうえでElasticsearch2.0のdockerコンテナが動いててAPIも叩けるようになった。

念の為、Macの生ローカルのElasticsearchの1.5を同時に起動してみる。

22:07:13 (๑˃̵ᴗ˂̵)و ./elasticsearch-1.5.1/bin/elasticsearch
21:56:08 (๑˃̵ᴗ˂̵)و curl ":9200"
{
  "status" : 200,
  "name" : "Frank Simpson",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.5.1",
    "build_hash" : "5e38401bc4e4388537a615569ac60925788e1cf4",
    "build_timestamp" : "2015-04-09T13:41:35Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.4"
  },
  "

これで、生ローカルのElasticsearchと分離して、docker-machine ssh es2.0でいつでもElasticsearch2.0のAPIを叩いて試せる環境が整ったでござるな。

DRYな備忘録

SlackのAPIでチャンネルに投稿

ライブラリじゃなくてcurlのサンプルがほしい

追記

  • POST 限定になってたので注意
    • しかもGETだとエラーメッセージがchannel_not_foundとかになって「は?」ってなるので注意

以下原文

# 都合により改行
% curl "https://slack.com/api/chat.postMessage
?token={下記参照}
&text=あ
&channel=mychat
&as_user=true"

上記のtokenは、下記のリンクからチームごと自分のトークンが発行できる。

あとはここから使いたいAPIを選んでください

今回は、シンプルに発言したいので以下を使った

DRY