- 投稿日:2020-07-14T22:24:13+09:00
【Go】踏み台サーバー経由 (ssh) で VPC エンドポイントの Elasticsearch Service にローカルから接続する【AWS】
やりたいこと
踏み台サーバー経由 (ssh) でプライベートサブネット内の RDS (MySQL) へ接続するのと同様にして、VPC エンドポイントの Elasticsearch Service にも踏み台サーバー経由で接続することが目標です。
ssh コマンドと curl コマンドを利用すれば次のように簡単にローカルから接続できますが、今回は Go プログラムで接続することを目標とします。
ssh -i <path/to/private-key> <username>@<hostname> curl -s '<ES_ENDPOINT>/_cat/indices?format=json&pretty'前提
次のようなアーキテクチャを想定します。セキュリティグループはいい感じに設定されているものとします。
実装
Elasticsearch のクライアント用ライブラリとしては Elastic 社公式の elastic/go-elasticsearch を利用します。
実装のポイントは
http.RoundTripper (http.Transport)
の Dial に SSH Client の Dial を利用することです。package main import ( "context" "encoding/json" "fmt" "io" "io/ioutil" "log" "net" "net/http" "os" "time" "github.com/elastic/go-elasticsearch/v8" "github.com/elastic/go-elasticsearch/v8/esapi" "golang.org/x/crypto/ssh" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) var ( sshUser = os.Getenv("SSH_USER") sshHost = os.Getenv("SSH_HOST") sshPort = os.Getenv("SSH_PORT") sshPrivateKey = os.Getenv("SSH_PRIVATE_KEY") esEndpoint = os.Getenv("ES_ENDPOINT") ) // ------------------------------ // 秘密鍵ファイルの読み込み // ------------------------------ b, err := ioutil.ReadFile(sshPrivateKey) if err != nil { log.Fatal(err) } signer, err := ssh.ParsePrivateKey(b) if err != nil { log.Fatal(err) } // ------------------------------ // SSH クライアントの生成 // ------------------------------ sshConf := ssh.ClientConfig{ User: sshUser, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, Timeout: 10 * time.Second, } sshClient, err := ssh.Dial("tcp", net.JoinHostPort(sshHost, sshPort), &sshConf) if err != nil { log.Fatal(err) } defer sshClient.Close() // ------------------------------ // Elasticsearch クライアントの生成 // ------------------------------ esConf := elasticsearch.Config{ Addresses: []string{esEndpoint}, Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: sshClient.Dial, // ここで SSH Client を利用 TLSHandshakeTimeout: 10 * time.Second, }, } es, err := elasticsearch.NewClient(esConf) if err != nil { log.Fatal(err) } // ------------------------------ // リクエストを実行 (/_cat/indices) // ------------------------------ req := esapi.CatIndicesRequest{ Format: "json", Pretty: true, } ctx := context.Background() resp, err := req.Do(ctx, es) if err != nil { log.Fatal(err) } defer resp.Body.Close() // ------------------------------ // レスポンスを解析 // ------------------------------ if resp.IsError() { log.Fatal(resp.String()) } body := io.TeeReader(resp.Body, os.Stdout) // debug var r []map[string]interface{} if err := json.NewDecoder(body).Decode(&r); err != nil { log.Fatal(err) } for i, obj := range r { fmt.Printf("\n[#%d]\n", i) for k, v := range obj { fmt.Println(k, v) } } }
- 投稿日:2020-07-14T21:10:04+09:00
AWS EC2 の golang 開発環境構築 - 2020
事前準備
- AWS EC2
- VSCode
Golang インストール
List$ amazon-linux-extras | grep golang 29 golang1.11=latest enabled \Install$ sudo amazon-linux-extras install -y golang1.11 $ go version go version go1.13.4 linux/amd64VSCode Plugin Install
Ctrl + Shift + P で ツールなどインストール
Hello World!
NewFile$ cd $GOPATH $ mkdir src $ cd src $ mkdir hello $ cd hello $ touch hello.goVSCode で開く
Remote Development 機能使ってますが、省略します。
Coding
Execute
Debug
- 投稿日:2020-07-14T15:16:34+09:00
Golangでマルチプログレスバーを表示したい!!
Golangの十八番と言えば並行処理なわけですが、それぞれの処理がどのくらいの進捗なのか知りたい!っということで、pbとuiprogressと言う人気のパッケージを使って動かしてみました。
Golangのプログレスバーを実装するパッケージについては、こちらの記事で紹介しています。
Golangでプログレスバーを表示するためのパッケージ3選pbでやってみた
main.gopackage main import ( "log" "math/rand" "sync" "time" "github.com/cheggaaa/pb" ) func processing(wg *sync.WaitGroup, b *pb.ProgressBar) { defer wg.Done() for n := 0; n < 200; n++ { b.Increment() rand.Seed(time.Now().UnixNano()) time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) } b.Finish() } func main() { process_1 := pb.New(200).Prefix("process_1: ").SetMaxWidth(80) process_2 := pb.New(200).Prefix("process_2: ").SetMaxWidth(80) process_3 := pb.New(200).Prefix("process_3: ").SetMaxWidth(80) pool, err := pb.StartPool(process_1, process_2, process_3) if err != nil { log.Fatal(err) } var wg sync.WaitGroup for _, bar := range []*pb.ProgressBar{process_1, process_2, process_3} { wg.Add(1) go processing(&wg, bar) } wg.Wait() pool.Stop() }uiprogressでやってみた
main.gopackage main import ( "math/rand" "sync" "time" "github.com/gosuri/uiprogress" "github.com/gosuri/uiprogress/util/strutil" ) func processing(process_name string, wg *sync.WaitGroup, u *uiprogress.Bar) { defer wg.Done() u.PrependFunc(func(b *uiprogress.Bar) string { return strutil.Resize(process_name + ": ", 11) }) for u.Incr() { rand.Seed(time.Now().UnixNano()) time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) } } func main() { uiprogress.Start() process_1 := uiprogress.AddBar(100).AppendCompleted().PrependElapsed() process_2 := uiprogress.AddBar(300).AppendCompleted().PrependElapsed() process_3 := uiprogress.AddBar(200).AppendCompleted().PrependElapsed() var wg sync.WaitGroup wg.Add(1) go processing("process_1", &wg, process_1) wg.Add(1) go processing("process_2", &wg, process_2) wg.Add(1) go processing("process_3", &wg, process_3) wg.Wait() }まとめ
なんか実際動かしてみると、Dockerでイメージをpullする時みたいでエモいですねw
どちらのパッケージも使いやすいので、馴染みのいい方を選んでカスタムするといいと思います。
- 投稿日:2020-07-14T12:15:25+09:00
【Go】 ユニットテストでflagへ引数を渡す際のハマりどころ
はじめに
はじめまして。Go初学者です。
Gopher道場#8や自身の勉強にて、気づいた学びを共有しています。今回は、あるCLIツールのユニットテストを実装する際のflag引数の渡し方と、
flag.Parse()
の実行位置によってテストが落ちる問題、またその理由についてまとめました。結論から
- ユニットテスト時は
flag.CommandLine.Set(name, value string) error
を利用すると実行時引数を渡す事ができる- ただし、
func init()
でflag.Parse()
を行っていると、テスト時の実行順序の関係上、期待しない動きをする事がある。testing
is 何
goのテストを書く際に利用する標準パッケージです。テストは
go test
コマンドで実行します。
一般的に、テスト対象となるファイルex) main.go
の末尾に_test.go
を付けてex) main_test.go
テストコードを記載しています。main.gopackage main func square(i int) int { return i * i } func main() {}main_test.gopackage main import "testing" func Test_square(t *testing.T) { tests := []struct { name string i int want int }{ {name: "1", i: 1, want: 1 }, {name: "2", i: 2, want: 4 }, {name: "3", i: 3, want: 9 }, {name: "4", i: 4, want: 16 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := square(tt.i); got != tt.want { t.Errorf("square() = %v, want %v", got, tt.want) } }) } }The Go Playgroundのデフォルトセットにも、サンプルがありますね。
flag
is 何
コマンドラインフラグを扱う際に利用する標準パッケージです。
./square -target=10
といった形で、-target
に対して値を渡す形でコマンドを実行したい場合に利用します。
以下は、flag.IntVar
を利用してコマンドラインフラグを指定し、flag.Parse()
を実行することで指定した値を受け取り、パッケージ変数i
へ格納しています。main.gopackage main import ( "flag" "fmt" "os" ) var ( i int ) func init() { flag.IntVar(&i, "target", 0, "Enter an integer to the square (not 0)") } func square(i int) int { return i * i } func run() int { flag.Parse() if i == 0 { fmt.Println("input 0") return 1 } fmt.Println(square(i)) return 0 } func main() { os.Exit(run()) }ユニットテストでflagへ引数を渡す
前述したソースの
run()
関数をテストします。
テスト関数内で、flag.Commandline.Set(name, value string) error
を利用します。
その後、テスト対象のrun()
関数内でflag.Parse()
が実行される事で、パッケージ変数に値を格納することができます。main_test.gofunc Test_run(t *testing.T) { tests := []struct { name string i int want int }{ {name: "input0", i: 0, want: 1}, {name: "input1", i: 1, want: 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { flag.CommandLine.Set("target", strconv.Itoa(tt.i)) // -target=iと指定したかの様に設定できる if got := run(); got != tt.want { t.Errorf("run() = %v, want %v", got, tt.want) } }) }ユニットテスト時のハマリどころ
上記のflagの例ですが、サラっと
init()
関数を利用しています。
こちらはパッケージの初期化の際に動く関数で、パッケージ変数の複雑な初期化などに利用します。 詳細は公式ドキュメントへ
今回はコマンドラインフラグ-target
を指定する必要があったため、init()
関数に記載しています。
当初は以下の通りinit()
を記述していました。main.gofunc init() { flag.IntVar(&i, "target", 0, "Enter an integer to the square (not 0)") flag.Parse() }この様に記述した場合、後述する実行順序の関係で、下記の通りtestそのものが動作しなくなってしまいます。
$ go test flag provided but not defined: -test.timeout Usage of /var/folders/53/j681dgkd1wb7jjyvg8vkwf8h0000gp/T/go-build009487560/b001/aggreagtemyqiita.test: -target int Enter an integer to the square (not 0) exit status 2 FAIL aggregate-my-qiita/cmd/aggreagtemyqiita 0.300sエラー内容を見る限り、どうやらflagパッケージが関係していそうですね。
テストの実行順序について
こちらの記事に詳細が書かれています。
go test
を実行する際は、メイン関数そのものを実行しテストフラグを付与する事で、テストを実行しているようです。また、Go1.13リリースノートに以下の通り記載されていました。
Testing flags are now registered in the new Init function, which is invoked by the generated main function for the test. As a result, testing flags are now only registered when running a test binary, and packages that call flag.Parse during package initialization may cause tests to fail.
テスト用に生成されたメイン関数によって呼び出される新しい Init関数にテストフラグが登録されるようになりました。その結果、テストフラグはテストバイナリの実行時にのみ登録されるようになり、パッケージの初期化中に flag.Parse を呼び出すパッケージではテストが失敗する可能性があります。
つまり、私のハマったポイントとしては、
init()
関数にflag.Parse()
を記載してしまったため、該当のパッケージのテストにおいて、
テスト用のメイン関数のflagセットの前にflag.Parse()
が実行されてしまったため、前述のエラーが出たと解釈しました。
間違っていたらご指摘ください。おわりに
ハマった事で、
go test
が何をしているか、Goの初期化から実行までの順序について学ぶ事ができました。
GoはGoで書かれているので、いずれソースコード上で上記の流れを追ってみたいと思います。参考
- 投稿日:2020-07-14T09:27:09+09:00
Goでクッキーをブラウザに渡したり取得したりしてみる
Goの標準パッケージを使ってクッキーの操作をやってみました。
クッキーをブラウザに渡す
コードはこんな感じです。
main.gopackage main import ( "net/http" "log" ) func setCookie(w http.ResponseWriter,r *http.Request){ c1:=http.Cookie{ Name: "cookie1", Value:"Hello World!", HttpOnly: true, } c2:=http.Cookie{ Name:"cookie2", Value:"Hello Quita!", } http.SetCookie(w,&c1) http.SetCookie(w,&c2) } func main(){ http.HandleFunc("/set_cookie",setCookie) log.Fatal(http.ListenAndServe(":8080",nil)) }実行して localhost:8080/set_cookie をブラウザで開くと一見なにも変化がないこのような画面になります。
しかし、Chromeの検証でApplication>Storage>Cookiesを見るとこのようにクッキーがブラウザに渡せていることがわかります。
次は逆に渡したクッキーをブラウザから取得し、画面に出力してみます。
クッキーをブラウザから取得
コードはこちらです。
main.gopackage main import ( "fmt" "log" "net/http" ) func getCookie(w http.ResponseWriter,r *http.Request){ //名前が"coockie1"であるクッキーを取得 c1,err:=r.Cookie("cookie1") if err!=nil{ log.Fatal(err) } //c1を出力 fmt.Fprintln(w,c1) //クッキーをスライスで全取得 cSlice:=r.Cookies() //cSliceを出力 fmt.Fprintln(w,cSlice) } func main(){ http.HandleFunc("/get_cookie",getCookie) log.Fatal(http.ListenAndServe(":8080",nil)) }http://localhost:8080/get_cookie にアクセスするとこのように先ほどブラウザに渡したクッキーを取得できていることがわかります。
メソッドCoockies()はすべてのクッキーをスライス形で返します。
参考