20191002のGoに関する記事は11件です。

golangにおけるJSONPの解析

How would I parse JSONP!!

https://www.reddit.com/r/golang/comments/a1oqxp/how_would_i_parse_jsonp/
上記は、
callback関数に囲まれたJSONPの形で返ってくるんですけど......
Unmarshalできなくて困るんですけど......
どうやってJSONP to JSON to Structしましょう:sweat:...という記事です。

例)

func main() {
  jsonpStr := `
  callback({
    "hello": "world!!",
    "num": 123456
  });`

  var v interface{}
  err := json.Unmarshal(jsonpStr, &v)
  fmt.Println(err)
  // invalid character 'c' looking for beginning of value
  // JSONを期待しているのに先頭にcallbackという不要な文字列があるためUnmarshalがエラーを返します。
}

対応方法

一番上に乗せたredditの投稿における回答より二つ紹介します。

titpetricさんの対応方法

先頭から(までと、後ろから)までをtrimしちゃえYO!という方法です。

    jsonpStr := `callback({
      "hello": "world!!",
      "num": 123456
    });`

    jsonStr := jsonpStr[strings.Index(jsonpStr, "(")+1 : strings.Index(jsonpStr, ")")]
    fmt.Println(jsonStr)
    // {
    //  "hello": "world!!",
    //  "num": 123456
    // }

    var v interface{}
    json.Unmarshal([]byte(jsonStr), &v)
    fmt.Printf("%+v", v)
    // map[hello:world!! num:123456]

jerfさんの対応方法

io.ReaderのWrapperとして用意しようYO!という方法です。

type JSONPWrapper struct {
    Prefix     string
    Underlying io.Reader

    gotPrefix bool
}

func (jpw *JSONPWrapper) Read(b []byte) (int, error) {
    if jpw.gotPrefix {
        return jpw.Underlying.Read(b)
    }

    prefix := make([]byte, len(jpw.Prefix))
    n, err := io.ReadFull(jpw.Underlying, prefix)
    if err != nil {
        return n, err
    }

    if string(prefix) != jpw.Prefix {
        return n, fmt.Errorf("JSONP prefix mismatch: expected %q, got %q",
            jpw.Prefix, prefix)
    }

    // read until the (; in general, this should just be one read
    char := make([]byte, 1)
    for char[0] != '(' {
        n, err = jpw.Underlying.Read(char)
        if n == 0 || err != nil {
            return n, err
        }
    }

    // We've now consumed the JSONP prefix.
    jpw.gotPrefix = true
    return jpw.Underlying.Read(b)
}

func main() {
    jsonpStr := `callback({
      "hello": "world!!",
      "num": 123456
    });`
    prefix := "callback"
    jsonp := bytes.NewBuffer([]byte(jsonpStr))
    var decoded interface{}
    decoder := json.NewDecoder(&JSONPWrapper{Prefix: prefix, Underlying: jsonp})

    // This code depends on the fact the JSON parser stops when it
    // finishes a JSON object, so we don't have to handle the concluding
    // paren.
    decoder.Decode(&decoded)
    fmt.Println(decoded)
    // map[hello:world!! num:123456]
}

json.NewDecoderの引数にJSONPWrapper(io.Readerインターフェースを満たした)を渡すことにて、
decoder.DecodeのタイミングにてJSONWrapper.Read()が実行されます。
JSONWrapper.Read()の中では、Prefixに設定された文字列をjsonp文字列の先頭から比較して削除しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UbuntuでGo書きたい男

11月に転職が決まりました。
次の会社から「Go言語とAWSを調べてきて」というざっくりした指令をいただき、それを遂行したい男、私です。

とりあえずGo!!

まずはGo言語の勉強からやろうと思います。
言語の特性を知るだけならまぁそんな時間かからんでしょー

Ubuntuで書きたいわ〜

Windows嫌いです。特に10から。理由はOSアップデートだったり、アカウント切り替えだったり、設定の面倒さだったり。
Macもいいですが、新しいのは高くて買えないので既存のWindowsノートをUbuntu18.04LTSに入れ替えました。
最初はGNOME3で使ってたんですが、KDEが気になったのでそっちに変更。今のところいい感じ。

環境設定

・自前ファイルサーバーをマウント
・仮想デスクトップを2x2に設定
・電源管理でディスプレイが暗くなるまでの時間とかを伸ばす
・.vimrc設定
など

ショートカットキー

・ターミナル起動
・仮想デスクトップ上下左右に移動
・ウィンドウを仮想デスクトップ上下左右に移動
など
Ubuntu設定画面

参考

何も考えず~/.vimrcにこれを書くんだ! 〜vim初心者によるvim初心者のためのvim入門〜

Go環境を入れたいわ〜

UbuntuにGoを入れてパス通すマン

参考

Go言語をUbuntuに導入する

Atomかっこいいわ〜

エディタはAtom使います。かっこいいからですね。
本体、日本語化パッケージ、Go用のパッケージをインストール。

参考

テキストエディタ「Atom」のインストールから日本語化まで
Go言語はじめました

できたわ〜

ほれ。
Atom画面
結局全部引用なんですけどね。一連の流れが重要ってことで。
これからこの環境で色々書いてみます。さいなら。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goの練習 PHPerが業務用Webアプリケーションでありそうな処理をGoと速度比較してみる

はじめに

PHPerとして仕事を始めて6年半(2019年現在)。様々な現場で業務用Webアプリケーションの構築に携わってきました。
Webアプリケーションであれば業務で使うような大体の機能はPHPで実現できるのですが、1システムに1機能はPHPだと物足りないというか、痒い所に手が届かないなと思う箇所はどうしても出てきます。

顕著にそれを思うのは大量データの処理などのパフォーマンスを発揮したい機能です。
PHP7の登場でPHP5と比較して格段にパフォーマンスが向上しました。ですがそれでももっと速く処理したいと思う場面がしばしばあります。

そこで自身のスキルの幅を広げることも含めて、スクリプト言語のように記述出来てハイパフォーマンスが期待できるGoを学んでみようと思いました。

本記事の概要

経験上Webアプリケーションでありそう、かつ処理が重そうな機能を考えた結果、CSVアップロード/ダウンロード機能が思いつきました。
今回はフロント部分は省略して、コマンドラインでCSVをDBに保存する機能とDBからCSVを作成する機能をGoとPHPで作成し、そのパフォーマンスの差を検証します。
諸先輩方が通ってきた道かと思いますが、自学のため。

機能の概要

アップロード

  • あらかじめ用意したCSVを読み込み、DBにアップロードする。
  • 指定した件数をひとかたまりとして処理する
  • 新規追加フラグを設け、これが1の場合は新規追加とみなしDBとの重複チェックを行う。

使用したCSVは国交省提供の位置参照情報ダウンロードサービスから全自治体のデータをダウンロードし加工したものになります。
およそ21万件ほど。

UPSERTをするようにしたので結局重複チェックは意味をなさないのですが、それっぽく作るため入れてみました。

ダウンロード

  • DBからデータを全量取得し、CSVに出力する。
  • 指定した件数をひとかたまりとして処理する

こちらの方が純粋なパフォーマンス比較ができそうです。

両方の機能でGoでは並行処理での処理を指定可能。並行処理では2スレッドで処理します。

やらないこと

細かいことは抜きにする方針にします。

  • フロント部分(GUI)の作成
  • 細かいバリデーション
    • アップロード用CSVの存在チェック
    • 形式チェック
  • SQLインジェクション対策

等々

ハマったら容赦なくOSSライブラリを使おうと思います。
逆に言えばハマるまでは標準パッケージで頑張ります。

実行環境等

サーバ
Amazon Linux 2 AMI (HVM), SSD Volume Type
t2.medium(CPU 2コア メモリ 4GB)

言語
go v1.13
php v7.2.22

DB
MySQL 5.7

AmamzonLinuxの上にそれぞれのアプリケーションとDBのDockerコンテナを建てて実行しています。

実行結果

ソースは→ GitHub

GNU版timeコマンドを利用しています。Goはコンパイル済みのファイルを実行。

まずはアップロードから。重複エラーがないパターン。

言語 real user sys 消費メモリ
Go 7.91s 1.00s 0.05s 3.62MB
Go(並行処理) 4.18s 1.00s 0.09s 5.65MB
PHP 7.00s 1.61s 0.11s 6.32MB

全件重複エラーするパターン。

言語 real user sys 消費メモリ
Go 8.46s 1.20s 0.30s 3.62MB
Go(並行処理) 5.27s 1.23s 0.23s 6.18MB
PHP 8.91s 1.90s 0.15s 16.99MB

次はダウンロード

言語 real user sys 消費メモリ
Go 0.92s 0.74s 0.05s 2.42MB
Go(並行処理) 0.70s 0.82s 0.04s 3.97MB
PHP 1.07s 0.54s 0.24s 7.47MB

グラフにしてみます。


実際に処理にかかった時間。意外とPHPと差はない、どころか逐次実行の場合はGoがPHPより1秒ほどかかっています。並行処理の場合は流石に速いですね。


CPUが処理をした時間。並行処理と逐次処理の間に大きな差はなし。アップロードにおいてはPHPの方が占有時間が長いようです。


OSがシステムコールに使った時間。正直なところ語れるほど詳しくないのですが場合によりけりといった印象です。


消費メモリ。Goの方が軒並み少ないですね。

所感

測定結果に関して

  • 当初想定していたよりもGoとPHPの差はあまりありませんでした。実際の処理時間に焦点を当てると場合によってはPHPの方が速かったりということもあり、驚きました。
    私自身がGo初学者というのもあり、パフォーマンスチューニングができていない可能性も高いです。
    ただし並行処理になるとやはりというべきか結構な時間短縮になりますね。

  • メモリ消費量はGoの方がパフォーマンスが良さそうなので、同じ処理でもサーバの負荷を抑えられるなどのメリットはありそうです。
    ここはスクリプト言語とコンパイル形式の言語の違いもありそうですが。

  • データ量が増えたりするとまた差がでたりするのでしょうか。

実装に関して

  • Scan関数の引数で取得したカラムを一つ一つ指定して変数に入れてあげないといけないのは少し面倒。
  • DBから取得したデータをうまいことCSVに書き出す方法を見つけるのにハマったので結局gocsvに頼りました。
  • エラーハンドリングはもう少ししっかりやった方がいいかなと思いました。特に問答無用のexitは実務レベルではあまりやらないと思います。
  • アップロードで指定した件数をひとかたまりとしてに重複チェックと投入をしていますが、1件でも重複チェックに引っかかったかたまりは投入されず、引っかからなかったかたまりは投入されるというよくわからない状況になっています。業務では引っかからなかったデータだけ投入するとか、エラーが1件でもあったら投入しないという仕様にするのが妥当だと思います。完成してから気づきました… 今回は重複チェック意味ないのでご容赦を。
  • 今回Goの実装に費やした時間は余暇時間を使って環境構築したりハマったり調べたりを繰り返し、大体1週間ぐらいでできました。時間がかかるのは想定していましたが、それでも結構時間がかかった印象です。本人のスキルが多分にありそうですが、毛色の違う言語から来たときの学習コストがなんとなくわかりました。PHPの方は1時間もかからずにできました。

なんとなく触ることはできたので、次は何か開発してみたいと思います。いつできるかはわかりませんが。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cli-kintoneでやってみた「後で追加した計算フィールドに一括で値を反映する方法」

cybozu developer network の kintone Tips に、
後で追加した計算フィールドに一括で値を反映する方法
という記事があります。

今回、Go製の kintoneコマンドラインツール cli-kintone を使って、コードを書かずワンライナーで自動計算フィールドに値を一括して反映する Tips を公開します。

動作環境

  • macOS 10.13.6
  • cli-kintone 0.9.4

cli-kintone の準備

cli-kintoneのZipファイルをダウンロード、解凍後に実行ファイルをパスの通ったディレクトリに配置します。

https://github.com/kintone/cli-kintone
https://github.com/kintone/cli-kintone/releases

kintoneアプリの準備

テスト用のkintoneアプリを用意します。

営業支援パック導入

スペースを作り、アプリストアから営業支援パックをサンプルデータを含めて作成します。

FireShot Capture 007 - 営業支援パック - kintone アプリストア - od4sa.cybozu.com.png

案件管理に集計用年月フィールドを追加

文字列1行フィールドを追加して、自動計算を設定します。

FireShot Capture 004 - アプリの設定 - od4sa.cybozu.com.png

FireShot Capture 005 - アプリの設定 - od4sa.cybozu.com.png

一覧の項目に追加したフィールドを表示させておきます。

FireShot Capture 006 - 案件管理(営業支援パック) - レコードの一覧 - od4sa.cybozu.com.png

考え方

下記の仕組みにより計算フィールドのみの更新を可能としています。

cli-kintoneによる処理

処理の流れです。

  1. レコードIDのみを取得します
  2. 取得したレコードIDを読み込みます

処理コードと結果

$ cli-kintone -a 123 -t API-Token -d Domain -c "\$id" | cli-kintone --import -a 123 -t API-Token -d Domain
Start from lines: 1 - 11 => SUCCESS
DONE

cli-kintoneの1,2の処理をパイプで繋げて実行する事で、新規に追加した自動計算フィールドが更新されます。

FireShot Capture 008 - 案件管理(営業支援パック) - レコードの一覧 - od4sa.cybozu.com.png

参考リンク

——

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang - Makefileの書き方

はじめに

Golang開発のタスクランナーとしてMakefileを利用することが多いようなので書き方を整理します。

サンプル

https://github.com/so-heee/golang_example/tree/master/makefile_example

タスク

タスクの実行

terminal
$ make taskname

タスクの設定

Makefile
.PHONY: taskname
taskname:
    command

# 実行コマンド非表示
.PHONY: taskname
taskname:
    @command

# タスク定義が複数のコマンド
.PHONY: taskname
taskname:
    command1
    command2

# 複数コマンドをワンライナーで実行
.PHONY: taskname
taskname:
    command1 && command2

# 上のケースの別の方法
.PHONY: taskname
taskname:
    command1 ; command2 ;\
    command3

# タスクの前に別のタスクを実行
.PHONY: taskname3
taskname3: taskname1 taskname2
    command

# 自身のMakefileに定義してある別のタスク定義を実行
.PHONY: taskname2
taskname2:
    $(MAKE) taskname1
    command

.PHONYについて

タスク名と同名のファイルやディレクトリがあるとタスクが実行されません。
.PHONYは省略可能ですが、定義することで上記の条件でも実行が可能になるため
基本的には書いていこうかと思います。

変数

Makefile
# 変数名=値
VAR=hello make
.PHONY: taskname
taskname:
    echo $(VAR)

# shell commandの実行結果を変数に格納
NOW=$(shell date)
.PHONY: taskname
taskname:
    echo $(NOW)

# シェルスクリプト変数・環境変数は$$で参照
.PHONY: taskname
taskname:
    VAR=$$GOPATH && echo $$VAR

# タスク実行中に環境変数を書き換えたい場合は、コマンド実行が1行終わるごとに環境変数がmakeコマンド実行時に戻る
.PHONY: taskname
taskname:
    export VAR="この値は消えてしまいます"
    echo VAR=$${VAR}

.PHONY: taskname
taskname:
    export VAR="この値は残ります" ;\
    echo VAR=$${VAR}

# サブディレクトリのMakefileのタスクを実行する
.PHONY: taskname
taskname:
    make -C sub_directory sub_task

# make実行時に変数の値を渡す(make task VAR=XXX)
VAR="これは上書きされる"
.PHONY: taskname
taskname:
    echo $(VAR) $$VAR

OSSプロダクトのMakefileを見てみる

Docker
kubernetes
Terraform
Gobot
Gin

.PHONYは全てのタスクで定義

参考のmakefileでは全てのタスクで.PHONYが定義されていました

一括で.PHONYを定義することもできる
.PHONY: bin cover default dev e2etest fmt fmtcheck generate protobuf plugin-dev quickdev test-compile test testacc testrace tools vendor-status website website-test

よく見かけたタスク

以下のタスクをよく見かけました

  • fmt
  • test
  • lint/vet
  • clean
  • help

タスクの詳細はshellを用意

複雑なタスクはshellを用意し、Makefileは実行だけするようにしていた箇所がいくつかありました

ifeqで条件分岐

Makefile
ifeq ($(XXX),y)
taskname:
    command1
else
taskname:
    command2
endif

参考

Go言語開発を便利にするMakefileの書き方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

システムプログラミング実践

これはなに?

Goで覗くシステムプログラミングの世界で学んだことをアウトプットする記事

システムプログラミングとは?

以下のような様々定義があるが、今回は一番下の「OSの提供する機能を使ったプログラミング」をシステムプログラミングの定義として進める。

  • C言語によるプログラミング
  • アセンブリ言語を意識したC言語によるプログラミング
  • 言語処理系(インタプリタを含む)、特にネイティブコードを生成するコンパイラの開発
  • OS自身のプログラミング
  • OSの提供する機能を使ったプログラミング

OSの機能について

一般的なコンピュータに搭載されているOSについて、その機能の最大公約数をとれば、次の機能に集約されるでしょう。

  • メモリの管理
  • プロセスの管理
  • プロセス間通信
  • ファイルシステム
  • ネットワーク管理
  • ユーザ管理(権限など)
  • タイマー

Go言語

Go言語は、C言語の性能とPythonの書きやすさ/読みやすさを両立させ、モダンな言語特徴をうまく取り入れた言語となることを目標にGoogleが開発したプログラミング言語。

スクリーンショット 2019-10-01 22.07.03.png

スクリーンショット 2019-10-01 22.07.03.png

import "fmt"

func main(){
  fmt.println("hello world¥n")
}

デバックをしていく事により、上記プログラムがシステムコールの呼び出しをしていることがわかる。

スクリーンショット 2019-10-02 16.02.22.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語からシェルスクリプトが実行できない(シェバングの必要性)

概要

Go言語を用いてシェルスクリプトを実行しようとしたところ、エラーになり実行できなかった。
lsなどコマンド単体での実行はでき、シェルスクリプトのみ実行できなかった。
シェルスクリプト自体は、コンソールログインした状態では実行できる状態であった。

原因としては、シェルスクリプトにシェバングがないためであった。
シェルスクリプトにシェバングを追加することで、実行可能となった。

実行されるシェルスクリプト

test.sh
date "+%Y.%m.%d-%H.%M.%S" >> test.log

シェルスクリプトを実行するGoプログラム

gotest.go
package main

import (
    "os/exec"
    "fmt"
)

func main() {
        out,err := exec.Command("/test.sh").Output()

        if err != nil {
                fmt.Println("Command Exec Error.")
        }

        fmt.Printf("result: \n%s", string(out))
}

エラー原因

シェルスクリプトにシェバングが記載されていないため。
コンソールからログインしている場合は、ログイン時のシェルがいい感じにスクリプトを読み取ってくれるため、
シェバングがなくとも、シェルスクリプトは実行できる。

Goから実行する場合は、新規プロセスの生成となるため実行可能ファイル以外では、何らかの手段で起動方法を連携する必要がある。
シェバングがないと、なにも情報がないため、実行が不可能となっていた。

対応策

シェルスクリプトにシェバングを追加した。

test.sh
#!/bin/sh   <= 追加
date "+%Y.%m.%d-%H.%M.%S" >> test.log
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gofeed利用時に発生した「Failed to detect feed type」の対応

現象

Cloud Functionsを利用して、gofeedでブログをパースしようとしたところ、以下のメッセージと共にエラーが発生。

  Failed to detect feed type
main.go
  fp := gofeed.NewParser()
  feed, _ := fp.ParseString(xmlData) // ここでエラー

原因切り分けのために条件を変えて実行したところ、以下がわかった。

  • エラーが発生しないブログもある
  • ローカルで実行するとエラーにならない

環境

  • Golang 1.11.0
  • gofeed v1.0.0-beta2
  • Google Cloud SDK 264.0.0

原因

httpアクセスする際のCloud FunctionsのIPアドレスがブログ側からブロックされてしまったためと思われる。

対応

Cloud Functionsのregionをasia-northeast1からus-east1に変更したところ、正常に処理が完了した。

参考

https://github.com/mmcdole/gofeed/issues/75
https://github.com/mmcdole/gofeed/issues/96

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BindJSONとShouldBindJSONの違い(gin)

golang で gin を利用している際に、BindJSON と ShouldBindJSON があって名前が似ててどう違うのか気になったので記事にしてみます。間違ってたらご指摘していただけると嬉しいです。

ginについて

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
引用:https://github.com/gin-gonic/gin

Goで扱われている、HTTPのウェブフレームワークです。同じお酒の名前のフレームワーク、Martini(https://github.com/go-martini/martini ) よりもパフォーマンスが良いと評判みたいです。

BindJSON vs. ShoulBindJSON

以下にGoDocに基づく比較を書きます( https://godoc.org/github.com/gin-gonic/gin )

BindJSON ShoulBindJSON
原型:MustBindWith 原型:ShouldBindWith
指定されたバインディングエンジンを使用して、渡された構造体ポインターをバインドする。エラーが発生した場合、HTTP 400でリクエストを中止します。 指定されたバインディングエンジンを使用して、渡された構造体ポインターをバインドする。

つまりはこういうこと↓

ソースコード

context.go
//MustBindWith(BindJSON)
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
    if err := c.ShouldBindWith(obj, b); err != nil {
        c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
        return err
    }
    return nil
}

//ShouldBindWith(ShouldBindJSON)
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
    return b.Bind(c.Request, obj)
}

ShouldBindJSONに、400番でエラーを返す機能を含んでるのがBindJSONなんですね!

BindJSON と ShouldBindJSON のまとめ

今回学んだ違いは

BindJSON ShoulBindJSON
エラー処理の際に400を返してくれる エラー処理が内部では施されていない
独自エラーハンドリング不要 独自エラーハンドリング必要

エラーハンドリングを独自にやりたい場合は ShouldBindJSONを使うのが良さそうですね!
学びました。

参考

gin(GoDoc):https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kintoneコマンドラインツール cli-kintone を使ってkintoneクエリを理解する

説明

cli-kintoneは、kintoneアプリのデータをファイルに入出力することができるコマンドラインツールです。

Windows、Linux、Mac OS X 版があります。
https://github.com/kintone/cli-kintone

Zipファイルを解凍後に、実行ファイルをパスの通ったディレクトリに設置します。
コマンドラインから cli-kinton を打ち込んで Usage が表示されることを確認します。

日本語解説
https://developer.cybozu.io/hc/ja/articles/202957070

英語解説
https://developer.kintone.io/hc/en-us/articles/115002614853

cli-kintoneは Go言語で書かれています。ソースから実行ファイルを生成することも出来ます。

オプション一覧

Usage

Usage:
    cli-kintone [OPTIONS]

Application Options:
    -d=           Domain name (specify the FQDN) 必須。cybozuのドメインを指定します。例)  -d sample
    -a=           App ID (default: 0) (必須)アプリのIDを指定します。例) -a 123
    -u=           User's log in name (準必須)kintoneにアクセスする際にログインパスワードを使うときに指定 例) -u user 
    -p=           User's password(準必須)kintoneにアクセスする際にログインパスワードを使うときに指定 例) -p password 
    -t=           API token(準必須)kintoneにアクセスする際にAPIトークンを使うときに指定 例) -t APIToken 
    -g=           Guest Space ID (default: 0) (任意)kintoneのゲストスペースにアクセスする場合に指定
    -o=           Output format. Specify either 'json' or 'csv' (default: csv) (任意)出力形式を指定。省略時はCSVです。
    -e=           Character encoding. Specify one of the following -> 'utf-8'(default), 'utf-16', 'utf-16be-with-signature', 'utf-16le-with-signature', 'sjis' or
                    'euc-jp' (default: utf-8)(任意)出力形式の文字コードを指定。デフォルトはUTF-8です。
    -U=           Basic authentication user name (任意)アクセスするkintoneドメインにBASIC認証が設定されている場合に指定。パスワードとセットで使う。
    -P=           Basic authentication password(任意)アクセスするkintoneドメインにBASIC認証が設定されている場合に指定。ユーザー名とセットで使う。
    -q=           Query string (任意)アプリに渡すクエリ文字列。SQLライクな検索絞り込みが出来ます。例) -q "limit 10"
    -c=           Fields to export (comma separated). Specify the field code name (任意)出力する際のフィールドコードをカンマ区切りで指定。省略した場合は全てのフィールドが出力されます。例) -c "$id,文字列1行"
    -f=           Input file path(任意)CSVファイル等からレコードを追加・更新する場合にファイル名を指定。例) -f ./import.csv
    -b=           Attachment file directory (任意)添付ファイルをダウンロードする場合に、ダウンロードするディレクトリを指定。ディレクトリは自動作成される。
    -D            Delete records before insert. You can specify the deleting record condition by option "-q"(任意)レコードを削除する場合に指定。-q オプションを組み合わせて、クエリで絞り込んだ特定のレコードを削除することができる。
    -l=           Position index of data in the input file (default: 1)  (任意)インポートするファイルの読み込み位置を指定することができる。
        --import  Import data from stdin. If "-f" is also specified, data is imported from the file instead(任意)指定すると標準入力からデータを読み込む。
        --export  Export kintone data to stdout(任意)指定すると標準出力にレコードを出力する。

Help Options:(任意)cli-kintoneのオプションを表示する。
    -h, --help    Show this help message

実行例

テスト環境は macOS 10.13.6

APIトークンを使って最初の10行をCSVで取得する

cli-kintone -a 123 -t API-Token -d Domain -q "limit 10"

APIトークンを使って全てのレコードをCSVでバックアップする

cli-kintone -a 123 -t API-Token -d Domain > backup.csv

Mac OS X にてファイルを確認する
head backup.csv ファイルの先頭から10行表示
tail backup.csv ファイルの最後から10行表示

レコードIDのみをCSVで取得する

cli-kintone -a 123 -t API-Token -d Domain -c '$id'

または、ダブルクォーテーションで囲む場合は、$をバックスラッシュでエスケイプする。
cli-kintone -a 123 -t API-Token -d Domain -c "\$id"

シェルのパイプと組み合わせて、ヘッダー行のみを取得する

cli-kintone -a 123 -t API-Token -d Domain | head -1

シェルのパイプと組み合わせて、新規に追加した自動計算フィールドを更新する

自動計算フィールドを後で追加した場合、レコードの再保存を繰り返す事無く一気に更新を掛けます。

cli-kintone -a 123 -t API-Token -d Domain -c "\$id" | cli-kintone --import -a 123 -t API-Token -d Domain

ルックアップフィールドを持つアプリにレコードを追加する

ルックアップフィールドにはAPIトークン認証ではレコードの追加・更新ができません。この場合はパスワード認証を使います。

営業支援パックの「案件管理」にレコードを追加します。
フィールドコード顧客名がルックアップ、案件名が必須項目です。

importfile.csv
"顧客名","案件名"
"戸田ネットソリューションズ","新規案件1"
"金都運総研","新規案件2"
cli-kintone --import -a 123 -u 'userid' -p 'password' -d Domain -f ./importfile.csv
: Start from lines: 1 - 3 => SUCCESS
: DONE

スクリーンショット 2019-10-03 12.46.32.png

スクリーンショット 2019-10-03 12.46.48.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「GoとAWS CDKで作る本格SlackBot入門」を読んで、自分でもCloudWatch AlarmをSlack通知してみる

はじめに

先日開催された技術書典7の 「GoとAWS CDKで作る本格SlackBot入門」 という本の内容を受けて自分でもやってみました!本の内容に従いつつ、自分で少しアレンジしています。今回はツイートしたようにEC2のディスク使用量をSlack通知するBotを作ってみました。

現在は、下記サイトから、電子版を購入することできます、ぜひ今回の記事を読んで興味を持っていただけたら、購入してみてください。
AWS CDK、Goなどの技術を用いて、ChatOps(Slackなどのチャットを用いて快適に開発・運用していこー的なやつ)を導入することの素敵さが存分に味わえる本です。

前提

  • SlackAPIの用意ができていること
  • AWSアカウントとAWS CLIの設定ができていること
  • nodeの開発環境ができていること
  • Goの開発環境ができていること

技術スタック

  • Go 1.13
  • node 12.10.0
  • npm 6.11.3
  • typescript 3.6.3
  • AWS CLI 1.16.234
  • AWS(Lambda、SNS、CloudWatch)
  • AWS CDK(TypeScript) 1.9.0

AWS CDKとは

AWS CDK(Cloud Development Kit)は、CloudForamationテンプレートを、TypeScriptやJavaScript、Java、Python、.NETで生成することができます。

AWS CDKのメリット

  • 生のCloudForamationで書くよりも記述量を減らすことができる
  • コンポーネント分割が用意なので、保守性の高いインフラ設計が可能
  • アプリケーション開発で使っている便利ツールをインフラのコードでも使用できる

AWS CDKの導入

CDKの導入
npm install -g aws-cdk
CDKプロジェクトの作成
// プロジェクトのディレクトリに移動して
cdk init app --language=typescript

これを実行すると、以下のようにディレクトリが作成されます。

プロジェクトディレクトリ
.
├── README.md
├── bin
│     └── alert_ec2_disk_used.ts
├── cdk.json
├── jest.config.js
├── lib
│     └── alert_ec2_disk_used-stack.ts
├── node_modules
├── package-lock.json
├── package.json
├── test
│     └── alert_ec2_disk_used.test.ts
└── tsconfig.json

また、CDKのソースがCloudFormationテンプレートとなるため、S3バケットが必要ですが、これは事前にcdkの用意されたコマンドを実行することで CDK Toolkit StackとしてAWS環境にデプロイすることができます。
sh:CDK Toolkit StackをAWS環境にデプロイ
cdk bootstrap

必要なライブラリをインストールする
npm install @types/node@8 @aws-cdk/aws-lambda @aws-cdk/aws-sns @aws-cdk/aws-sns-subscriptions

1. CDKを書いてみる

lib/alert_ec2_disk_used-stack.tsを編集する

ここでは

  • Stack・・・デプロイの単位、リージョン、AWSアカウント
  • Constracts・・・デプロイするリソースの定義

を定義します。

Lambda周りの定義

lib/alert_ec2_disk_used-stack.ts(Lambda周りの定義)
// Lambda Function定義
import { Function, Runtime, Code } from "@aws-cdk/aws-lambda"
import { Topic, Subscription, SubscriptionProtocol } from '@aws-cdk/aws-sns';
import { LambdaSubscription } from '@aws-cdk/aws-sns-subscriptions';

/*
  中略
*/

const lambdaFunction: Function = new Function(this,
      "alert_ec2_disk_used_func", {
      functionName: "alert_ec2_disk_used_func",
      runtime: Runtime.GO_1_X,
      code: Code.asset("./lambdaSrc"),
      handler: "handler",
      memorySize: 128,
      timeout: cdk.Duration.seconds(10),
      environment: {
        "BOT_USER_TOKEN": "{SlackのBot User OAuth Access Token}",
        "CANNEL_ID": "{送信対象のSlackチャンネルID}",
        "MESSAGE": "ディスク使用量が75%を超えています。",
      },
    })

// Lambdaサブスクリプションの定義
const lambdaSub: LambdaSubscription = new LambdaSubscription(lambdaFunction)
lambdaSub.bind(snsTopic_Over75)

SNS周りの定義

lib/alert_ec2_disk_used-stack.ts(SNS周りの定義)
// SNSトピック定義
const snsTopic_Over75: Topic = new Topic(this,
  "ec2_disk_used_over_75", {
  displayName: "alert_ec2_disk_used_over_75",
  topicName: "alert_ec2_disk_used_over_75",
  })

// SNSサブスクリプション定義
const snsSub_Over75: Subscription = new Subscription(this,
  "ec2_disk_used_over_75_sub", {
    endpoint: lambdaFunction.functionArn,
    topic: snsTopic_Over75,
    protocol: SubscriptionProtocol.LAMBDA,
    })

bin/alert_ec2_disk_used.tsを編集する

ここでは、デプロイの定義(Lambdaソースのコンパイル、実行コマンドなど)を記載します。ここはほとんど本の内容を参考にしています。

bin/alert_ec2_disk_used.ts
#!/usr/bin/env node
import 'source-map-support/register';
import cdk = require('@aws-cdk/core');
import { AlertEc2DiskUsedStack } from '../lib/alert_ec2_disk_used-stack';
const util = require('util');
const exec = util.promisify(require('child_process').exec);

async function deploy() {
    //Go のソースを build する
    await exec('go get -v -t -d ./lambdaSrc/... && ' +
        'GOOS=linux GOARCH=amd64 ' +
        'go build -o ./lambdaSrc/handler ./lambdaSrc/**.go');

    const app = new cdk.App();
    new AlertEc2DiskUsedStack(app, 'AlertEc2DiskUsedStack');
    app.synth();

    //build 結果のバイナリを消去する
    await exec('rm ./lambdaSrc/handler');
}

deploy()

2. LambdaのGoソース

今回は特定のSlackチャンネルに「Incomming Webhooks」を利用して通知を投げます。今回はCDKの勉強がしたかったので、ここは割と雑。。。

handler.go(抜粋)
package main

import (
    "context"
    "encoding/json"
    "os"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/labstack/gommon/log"
    "github.com/pkg/errors"
)

type SNSMessages struct {
    AlarmName        string `json:"AlarmName"`
    AlarmDescription string `json:"AlarmDescription"`
    AWSAccountId     string `json:"AWSAccountId"`
    NewStateValue    string `json:"NewStateValue"`
    NewStateReason   string `json:"NewStateReason"`
    StateChangeTime  string `json:"StateChangeTime"`
    Region           string `json:"Region"`
    OldStateValue    string `json:"OldStateValue"`
}

const (
    EC2_DISK_USED_OVER_75_ALERT = "alert_ec2_disk_used_over_75"
)

const (
    GOOD    = "good"
    WARNING = "warning"
    DANGER  = "danger"
)

func main() {
    lambda.Start(noticeHandler)
}

func noticeHandler(ctx context.Context, snsEvent events.SNSEvent) (e error) {
    cannelID := os.Getenv("CANNEL_ID")
    footer := os.Getenv("FOOTER")
    var snsMessages SNSMessages

    for _, record := range snsEvent.Records {
        snsEvent := record.SNS
        snsMessage := snsEvent.Message

        err := json.Unmarshal([]byte(snsMessage), &snsMessages)
        if err != nil {
            log.Error(err)
            return err
        }

        switch snsMessages.AlarmName {
        case EC2_DISK_USED_OVER_75_ALERT:
            color := WARNING
            if err := PostToSlack(os.Getenv("MESSAGE"), color, cannelID, footer); err != nil {
                log.Error(err)
                return err
            }
            return nil
        default:
      return errors.New("想定するTopicではない")
        }
    }
    return nil
}
slack_poster.go
package main

import (
    "encoding/json"
    "fmt"
    "os"
    "strconv"
    "time"

    "github.com/nlopes/slack"
)

func PostToSlack(message string, color string, channel string, footer string) error {
    api := slack.New(os.Getenv("BOT_USER_TOKEN"))

    attachment := slack.Attachment{
        Color:  color,
        Text:   message,
        Footer: footer,
        Ts:     json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
    }

    _, _, err := api.PostMessage(channel, slack.MsgOptionAttachments(attachment))
    if err != nil {
        fmt.Println(err)
    }
    return nil
}

3. ビルドとデプロイ

CDKをビルドする
//.tsファイルを実行可能な.js ファイルにコンパイルします。
npm run build
スタックをデプロイする
cdk deploy
デプロイ完了するとこうなる。
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
 7/7 | 0:04:09 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | AlertEc2DiskUsedStack 

 ✅  AlertEc2DiskUsedStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:434353216964:stack/AlertEc2DiskUsedStack/8a3a46f0-e45c-11e9-b8b3-06680f8688ee
※スタックを削除する場合
cdk destroy {stack名}

4. 確認してみる

ちゃんとCloudFormation上でデプロイが成功していることが確認できます。
スクリーンショット 2019-10-02 0.11.00.png

やってみて

AWS CDKはすごいですね。CloudFormationでシコシコ書いていたのが、こんな簡単にIaCを実現できるとは。。。
ただ、CloudFormationとCDKを書く言語の両方の知識が必要なため、知識ゼロで始めるとなると学習コストが低いわけではないかな、と思います。

とはいえ、アプリ開発と同じ感覚でIDE等の恩恵を受けることができるので、快適に作業することができる点などが本当に魅力的です。マジで。

今回参考にした「GoとAWS CDKで作る本格SlackBot入門」では、AWSアカウント登録から、EC2の起動停止を行うSlackBot構築までを一つ一つ進めていくハンズオン形式になっています。
ここら辺の技術をあまり触ったことがない人でもAWS CDK、Go、ChatOpsの素晴らしさを体験することができるので、ここら辺の技術を使ってみたい方にはかなりオススメです!実案件にもすぐに導入することができる!

これからやってみたいこと

今回は、すでに構築してある EC2 と CloudWatchAlarm に対し、SNSTopic と Lambda をCDKで構築したので、次はカスタムメトリクス含めてイチからやってみたいです。(やり方はこれから調べる。。。)
あとは、これで一通りのサーバレスアーキテクチャを構築できるようになるところまでやろうと思います。

参考にしたもの

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む