go-openapi を書き直しています

本記事はGo2 Advent Calendar の20日目の記事です。昨日はyaegashiさんによる、jsonx.go でした。 皆さんはOpenAPI Specification というモノをご存じでしょうか。OpenAPI SpecificationはJSONまたはYAMLでREST APIを表現するための仕様で、現在バージョン3.0.2 が最新です。いわゆるSwagger の後継で、バージョン1系、2系がSwagger、3系以降がOpenAPI、ということになっています(Swaggerなら聞いたことがある/使っているという人も多いのではないでしょうか)。 OpenAPI Specificationは人間にも機械にも(比較的)読みやすい仕様書として、コード生成や、ドキュメントページの生成に使用することが可能です。 個人的には専らコード生成に使用しており、Go言語向けの実装としてgithub.com/nasa9084/go-openapi (以下go-openapi)を実装・公開しています。 go-openapiは2017年ごろから細々と実装を続けており、(多分)二番目か三番目には古いと思われるOpenAPIのGo実装です。 基本的にはただひたすらOpenAPIのオブジェクトをGoの構造体として定義、値のバリデーション関数を用意しているといったもので、特別な機能はほとんどありません。 YAMLのパーサも、go-yaml/yaml を使用しており、自前では実装していません。 そんな中、@goccy さんが、encoding/jsonとコンパチなインターフェースを持ったYAMLパーサを開発した、という話を耳にし、これを機に、とgo-openapiの実装を一から書き直し始めました。 もともと、パースは完全にgo-yaml/yamlに依存しており、Unmarshal系のメソッドも実装していなかった(途中から全部書くのはつらかったので・・・)ため、一部バリデーションに必要な関数を埋め込んだりもできなかったので、書き直したいとは思っていたのです。 現時点ではまだマージしておらず 、書いている途中なのですが、大きな変更点として次の様なものがあります。 もともとパブリックだったフィールドをすべてプライベートに変更し、ゲッターをはやした 各構造体に対応するUnmarshalYAML()メソッドをすべてコード生成するようにした YAMLパーサはgithub.com/goccy/go-yaml に乗り換えた rootオブジェクトを各構造体に埋め込むことで、バリデーションをとりやすくした 今後はよりコード生成に便利なメソッドを追加していきたいと思っています。 時間の都合で今日はここまで。技術的な話が全然無い記事になってしまった・・・

2019-12-20 · nasa9084

builderscon tokyo 2020やるぞ!という話

buildersconは「知らなかった、を聞く」あるいは英語で “Discover Something New"をスローガンとした、IT技術者向けの技術カンファレンスです。2016年、2017年、2018年、2019年と@lestrrat さんが主催として開催してきて、私もコアスタッフとして関わってきました。 そんなbuildersconですが、2020年は子育てで多忙な@lestrratさんに代わり、私(@nasa9084 )が主催として開催に向けて準備を進めていくことになりました! まぁ、今のところはやるぞ!という話だけで細かい話はこれからですが、皆さんどうぞよろしくお願いいたします。

2019-12-03 · nasa9084

net/http.ClientにHookをかける

昨日のこと。jszwedko/go-circleci というパッケージを使用してCircleCI EnterprizeのAPIを叩くという処理を実装していたのですが、どうにもうまくいかない。正直に言ってこのパッケージはドキュメントがしっかりしている、という訳ではないし、エラーメッセージを見ても何がだめなのか(そもそも現在使用しているCircleCI Enterpriseで使用できるかもよくわかっていなかった)わからない。 しかしまぁ、自分でHTTP requestを作ったりしてあれやこれややるのもまぁ面倒であるので、なんとかデバッグしたいと思ったのですが、外部のパッケージをフォークして変更を加えてデバッグする・・・という様なことはもちろんやりたくないわけです。 このパッケージは*http.Client を指定できます。*http.Clientはインターフェースではなく構造体なので、別の実装に置き換えるということはできません。が、その実装はほぼほぼ後述するhttp.RoundTripperなため、http.RoundTripperをラップして、HTTP requestとHTTP responseをログに吐けばまぁ、何が問題かわかるだろう、と考えました。 そんなモノは誰かがすでに書いているだろう、というのはさておき、http.RoundTripperを実際にいじってみるということはやったことがなかったので、勉強がてらnasa9084/go-logtransport なるものを書きました。 書いていく途中で、考えたことなど、記録に残しておくのも良さそうと思ったため、本記事とします。 http.Clientとhttp.RoundTripper Go言語でHTTPのリクエストを発行するには基本として*http.Client というものを使用します。簡便のため、GET 、POST 、Head についてはパッケージグローバルの関数も用意されてはいるのですが、これらも内部的にはパッケージグローバルで宣言されたDefaultClient という*http.Client が使用されています。 *http.Clientはゼロ値で使用できるようにまとめられた構造体で、DefaultClient は*http.Client{}と宣言されています。 普段はこの*http.Clientを使用してHTTPの通信を行うわけですが、実は*http.Clientはそれほど多くの機能は持っていません。実際、持っているフィールドはたったの4つ(Go1.13時点)しかないのです。*http.Clientはリダイレクトやクッキーなどの一部の処理だけを受け持っていて、実際のHTTP通信のほとんどはフィールドとして保持しているhttp.RoudTripperが行います。 http.RoundTripperはインターフェースとして定義されていて、自由に差し替えをすることができます。特に指定していない場合は*http.Transportがデフォルトの実装として使用されます。 Goの他の標準パッケージの例に漏れず、http.RoundTripperは非常にシンプルなインターフェースで、次の様に定義されています。 1 2 3 type RoundTripper interface { RoundTrip(*Request) (*Response, error) } RoundTrip()がHTTP requestを受け取り、HTTP responseを返します。つまり、requestのログをとり、子RoundTripperのRoundTrip()を実行し、Responseのログをとってそのまま返す、という様なラッパーを書けば良さそうです。 1 2 3 4 5 6 7 8 9 func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { // Requestのログをとる resp, _ := t.Transport.RoundTrip(r) // responseのログをとる return resp, nil } 実際にrequestとresponseのログをとるには、net/http/httputilパッケージのDump系関数が使用できます。今回はクライアント側の実装なので、httputil.DumpRequestOutとhttputil.DumpResponseを使用します。 テスト 実装の詳細はそれほど難しい内容ではないのでさておき、テストをどう書くか、という話をしましょう。 HTTPに関連したテストを書くとき、Go言語ではnet/http/httptestを使用すると便利です。 テストを書くにあたり、最初は子RoundTripperをモックして、適当にResponseを返すモノをつくればよいか、と思ったのですが、いい感じにテスト用のResponseを作成するのは面倒そうでした。 そういえば今日、http.Transportにロガーを仕込むの書いてみて、テストを書くのにRequestとResponse生成したいな・・・って考えてhttptestにないか見て、なんでないんや!って一瞬おこだったけどhttptest.Server使えばええんや、とすぐに思い直したのでアレがそれでそんな感じでした(とりとめが無い — nasa9084@某某某某(0x1a) (@nasa9084) October 31, 2019 しかし素直にhttptest.Serverを使えば、クライアント側では一切テスト用に特殊な実装を使用することなく、普通にリクエストをしてレスポンスを受けることができます(httptest.Serverは日常的に使っているのに、なぜ忘れていたのか・・・)。 これを使用して、シュッと適当なハンドラをサーブしてことなきをえました。 ...

2019-11-01 · nasa9084

空文字列確認は長さをとるべきか?

TL;DR s == ""とlen(s) == 0は等価 文字列比較か、長さ比較か Go言語で文字列が空かどうかを調べるには次の二つの方法があります。 1 2 3 4 5 6 7 8 9 // 1: 文字列を空文字列と比較する if s == "" { // do something } // 2: 文字列の長さが0かどうか調べる if len(s) == "" { // do something } 標準パッケージ・サードパーティパッケージともに、どちらの書き方も散見されます。 どちらを使うのが良いのでしょうか? 答えはどちらでも良いだそうです。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package benchmark_test import "testing" var somethingString = "hogehogefugafuga" func BenchmarkCompareString(b *testing.B) { for i := 0; i < b.N; i++ { if somethingString == "" { } } } func BenchmarkCompareStringByLength(b *testing.B) { for i := 0; i < b.N; i++ { if len(somethingString) == 0 { } } } このコードに対して go tool compile -Sした結果が以下。 BenchmarkCompareStringとBenchmarkCompareStringByLengthでは同じ内容となっています。 ...

2019-10-09 · nasa9084

Unuboでアプリケーションをサクッとデプロイする

Unubo というPaaSっぽいものが出てきたようなので触ってみました。 まだリリースされたばかりのようで、クレジットカードの登録もせずに無料で使用できます。 とりあえず適当にリポジトリを作って動かしてみました。 登録・ログインを済ませたら、使用したい機能を選択します。今回はGoのアプリケーションなので、AppsセクションにあるGoのアイコンを選択しました。 次の画面でアプリケーション名を入れ、デプロイ先のリージョンを選択します。 DBと接続する場合はDBと同一リージョンじゃなきゃ接続できないそうです。 最後に、細かい設定をできる画面になりますので、必要な情報を入力します。 ここでは、とりあえずGitHubとの連携をして、リポジトリを選択するだけであとは放置します。 最後にDeployボタンをポチッとしてデプロイされるのを待ちます。 どうやら、Deploy成功からアクセスできるようになるまで若干タイムラグがあるようなので、気長に待ちましょう。 有料プランが出てから価格設定や無料枠がどうなるかわかりませんが、まぁぼちぼち便利に使えそうですね。

2019-09-26 · nasa9084

技術書典7でGo言語の本を頒布します

今週末はいよいよ技術書典7ですね! 今回はGo言語の標準パッケージの解説本を頒布予定です。 web-apps.tech: サークル詳細ページ 当初の予定では全パッケージを詳解!みたいな予定だったんですが、ページ数が増えまくった結果、残念ながら26個しか詳解できませんでした。 108ページで一冊1,000円、電子版もあるよ、の予定です(電子版と両方での割引をどうしようか今考えてます)。 サークルスペースは「か11C」です。

2019-09-19 · nasa9084

社用Gitサーバでは社用のメールをつかう.gitconfig 〜社内ドメインを外に漏らさない編〜

GitHubのコミットログ、コミットした人のアイコンが出ていてとてもわかりやすいですよね。 コミットとアカウントの紐付けにはどうやら、コミットに紐付けられたメールアドレスが使用されているようです。そうなると、コミットに紐付ける(git config user.email=...とかやるアレです)メールアドレスはアカウントに登録してあるメールアドレスにしたいものです。 しかし、会社ではGitHub Enterprise(GHE)、私用ではgithub.com を使用している、と言った場合はどうでしょうか。コミットに紐付けたいメールアドレスがリポジトリによって変わる、ということになってしまいます。 調べてみると、そういった場合、次の様に.gitconfigにIncludeIfのブロックを設定することでうまく回避ができそうということがわかり、しばらく設定していました 1 2 [IncludeIf "gitdir:~/src/GHE_DOMAIN"] path = "~/.gitconfig.ghe" ~.gitconfig.gheには次の様に書かれています。 1 2 [user] email = 社用メールアドレス 私はGo言語をよく書くのと、ghq を使っている都合上、gitのリポジトリを配置するパスが$(GOPATH)/src/GIT_DOMAIN/USERNAME/REPOSITORYという形式になっているため、リモートリポジトリのドメインを指定することでうまいこと社用GHEの時だけ設定を上書きすることができていたのでした。 そしてこれは、どのPCでも使用できるように、dotfilesリポジトリ としてgithub.com にpushしていました。 その結果、会社の人から、「社内のサービスのURL(この場合はGHEのURL)はセキュリティ的な理由から外に出さないようにしてほしい」と連絡を受けました。すぐさま該当のブロックは消したのですが、そうするとメールの設定が自動でされなくなってしまい不便です。リポジトリのパスを変更するというのも、せっかくの統一的な操作に違いが出てしまい、不便です。 そこで思いついたのが、こういったセンシティブな情報を別のプライベートリポジトリに分け、Makefile でインストールを自動化するという方法です。 パブリックなdotfilesリポジトリ にある.gitconfig には次の様に書いてあります。 [include] path = ~/.gitconfig.secret .gitconfig.secretはその名の通り、秘匿情報を含んだ.gitconfigで、プライベート化されたdotfiles-secretリポジトリにおいてあります。dotfiles-secretリポジトリはmake installとしたときにgit cloneされ、さらにそのディレクトリ内のMakefileにより配置されます。dotfiles-secret/.gitconfig.secretには先ほどのIncludeIfブロックが書かれており、同リポジトリ内の.gitconfig.secret.ghe(名前を少し変えました)を読み込みます。 これで、全体の使い勝手をほとんど損なうことなくどのマシンでも(dotfilesが配備済みなら)同様に設定することができました。 Makefileは特にdotfilesのリストを持たないよう記述しているため、新しいdotfileが増えても、特にMakefileの変更をする必要も無く安心です。

2019-08-24 · nasa9084

hubコマンドにGitHub Enterprise環境を追加する

hub コマンドをご存じでしょうか。インストールしてalias git=hubと設定するだけで、gitコマンドからGitHubの操作ができるようになる優れものです。特に個人的にはgit createとするだけでGitHub上にリポジトリが作成される、というのが非常に便利だと思っています。 さて、皆さんの会社ではgitサーバはどのように構築されているでしょうか。いろいろな選択肢がありますが、それなりの規模だとGitHub Enterprise(以下GHE)を利用している、という会社も多いと思います。 実際、現職ではGHEを使っています。 そのような場合、趣味/個人の開発ではgithub.com、会社ではGHEと使い分けることとなりますが、GHEでhubが使えないとすると非常に不便です。そう考えて調べてみると、Web上では、hubをGHE環境で使うには環境変数を使うとする設定例が散見されます。 しかし実は、hubは複数環境での使用をサポートしているんです。 設定方法は至って簡単で、$HOME/.config/hubに設定を書き足すだけです。実際に見てみましょう。 すでにhubを使っている場合、cat $HOME/.config/hubで設定を見ることができます。私の場合、次のようになっていました(oauth_tokenは潰してありますが、実際にはトークンが入っています)。 1 2 3 4 github.com: - user: nasa9084 oauth_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx protocol: https どんな項目かは明らかですね。ここに会社のGHEの環境に関する情報を追記します。 まず、自社のGHEのアドレス以下/settings/tokensを開きます。Generate new tokenボタンをクリックし、新規でPersonal access tokenを発行します。名称はわかりやすい物を任意で付けてください。hubはリポジトリを操作するコマンドですから、scopeはrepoを与えれば十分でしょう。画面下部のGenerate tokenボタンをクリックすると、トークンが発行されますので、これをコピーしておきます。 手元のエディタ(お好みの物を使用してください)で$HOME/.config/hubを開き、設定を追加します。 1 2 3 4 YOUR_GHE_DOMAIN: - user: YOUR_USERNAME oauth_token: YOUR_TOKEN protocol: https オブジェクトのキーとしてGHEのドメインを、userはGHEでログインに使用するユーザ名を使用します。oauth_tokenに先ほど生成したPersonal access tokenを設定し、保存します。保存できたら、hubでGHEにアクセスができるようになっているはずです。 実際に使うと、次の様にどのホストを使用するか聞かれ、好きな方を選べるようになっています。 1 2 3 4 5 6 7 $ git create Select host: 1. github.com 2. YOUR_GHE_DOMAIN > 1 Updating origin https://github.com/nasa9084/REPOSITORY_NAME

2019-08-22 · nasa9084

Raspberry Pi zero W + IR

家で転がっていた使われないでいたRaspberry Pi zero Wを使って、家庭内のリモコン機器を自動化しようと思い立ち、秋葉原くんだりまで行って諸々部品を買ってきました。 ゴミを二つほど生成したのち、最終的に動くモノができましたので備忘録的に残しておきます。 pic.twitter.com/e55qxX5GL6 — nasa9084@某某某某(0x1a) (@nasa9084) July 29, 2019 主に「格安スマートリモコンの作り方 」を参考にしました。部品を購入した店は秋月電子で、商品ページの下の方にある「店舗情報」のリンクをクリックすると店内のどの棚に商品があるのかわかり便利です。どの部品も特に珍しい部品ではないため、商品自体がなくなっているようなことは(Pi zero用ユニバーサル基板をのぞき)まず無いように思えます。また、手持ちで0Ω抵抗が(なぜか)在庫してあったため、ジャンパ線代わりに使用しています。 一番見つけづらかったのがPi zero用ユニバーサル基板ですが、これは店の中ではなく、外のRaspberry Pi関連部品が置いてあるところにありました。 上記のQiita記事はさほど古いモノではないため、価格も変わってなかったように思えますが、動くモノができるまでにゴミを二つほど生成した都合上、三倍程度のコストがかかりました。回路周りや半田付けがあまり得意ではない人は覚悟(というほどの額ではないですが)しておいた方が(材料を余分に買っておいた方が)良いでしょう。 余談ですが部品を購入した際、近くのあきばおーでTranscendのmicro SDが安くなっていたため、大して使い道も考えずに32GBのモノを5枚ほど購入しました。 ほとんどの情報は参考にしたQiita記事にまとまっているため詳細は端折りますが、変更点として赤LEDを足してあります。ピカッと可視光が光るので、実行されたということを確認するのに便利です。 参考記事には実際の配線図だけはなかったので、紹介します。 上図が部品面、下図が半田面です。MOSFET 2N7000は平らな面が図の上側、MOSFET IRFU9024NPBFは放熱板(?)が図の下側、赤外線LEDは欠けている側(カソード?)が図の右側、赤LEDは欠けている側が図の左側、赤外線受光モジュールOSRB38C9AAは受光部が図の上側を向くように配置します。私は受光モジュールは図の下方向に向けて折り、受光部が天を向くような形にしました。 ブレッドボード上で配線してそのまま基板に移植したような形になっているため、複雑な配線もなく比較的簡単かと思います。 IRFU9024NPBFの左下はジャンパなので、半田面で配線してもかまいません(私は0Ω抵抗を使用した関係で部品面を通しています)。 実際に配線したもの。右側に温度センサを追加しています 赤外線コードの学習・実行も参考記事通りpigpioを使用しましたが、毎度SSHしてコマンドを実行するのは面倒なため、GoでAPIサーバを実装しました。Goで赤外線をGPIOでいい感じにアレするようなパッケージが見当たらなかったので、GoからPythonのスクリプトを叩くという残念なコードになっております。いいパッケージがあれば誰か教えてください。 systemdかなんかでデーモン化したりなんかして起動しておくと、例えば、 1 $ curl http://raspberrypi.local/playback?key=light:off とかすると部屋の電気が消せます。素晴らしい。 今のところ外部に公開してはいないのですが、外部に公開すると他のサービスとの連携(例えばIFTTT)ができないので、公開したい気持ちがあります。とはいえ、外からむやみに部屋の電気をあれこれされても困るし(だれもやらないとは思いますが)、そのまま公開するのも難しいな、と思っているところです。 とりあえずは扇風機とか、そのあたりをなんかいい感じにアレしたいですね。

2019-07-30 · nasa9084

NATSを触ってみた

NATS はCNCF (Cloud Native Computing Foundation)によってホスティングされているメッセージングシステムです。軽量で高パフォーマンスかつスケーラブルなのが特徴だそうです。オランダのSynadia社が中心となって開発を行っていますが、オープンソースソフトウェアなのでGitHub 上で今トリビュートすることもできます。 Go、NodeJS、Ruby、Java、C、C#、Nginx用のクライアントライブラリはSynadiaによってサポートされており、そのほかにもPythonやElixir用のクライアントなどが存在します。 NATSのサーバ自体(gnatsd)はGoで書かれている ため、バイナリ一つで起動できるほか、、公式Dockerコンテナイメージ やKubernetes用のOperator も用意されているため、簡単に構築・運用することができます。 本記事でも、Dockerで起動したサーバを使用しています。 NATSでは3種類のメッセージングモデルを利用することができます。 Publish/Subscribe Request/Reply Queueing 今回はPub/SubとRequest/Replyを試してみます。 サーバを立ち上げる 実験に先駆けて、まずはサーバを立ち上げます。今回はmacOS High Sierra環境のため、docker for macで起動してみます。 1 $ docker run --rm -d --name nats -p 4222:4222 -p 6222:6222 -p 8222:8222 nats:1.4.0-linux nats:1.4.0-linuxは執筆時点(2019-02-06)でnats:latestです。 ここで三つのポートを空けていますが、それぞれ用途は次の通りです。 :4222: client port :6222: route port :8222: http port それぞれの詳細な説明は割愛しますが、本記事ではクライアントからの接続だけを試してみますので、4222番ポートだけの開放でも問題ありません。 Publish/Subscribe まずは標準的なPub/Subモデルから試してみます。NATSのPub/SubはRedisなどと同様、Wikipedia でいうところの「トピックベース」なPub/Subです。NATSではトピックのことをSubjectとよびます。 NATSのSubjectは階層構造をとることができ、.(ドット)で区切って表現します。Subscriberはこの階層構造の一部にワイルドカードとして*(アスタリスク)を使用することができます。また、>を使用して下の階層すべて、を表現することもできます。 例えば、Subscriberがfoo.bar.*を購読している場合、foo.bar.bazやfoo.bar.quxなどのメッセージを受け取ることができますが、foo.bar.baz.quxは受け取ることができません。一方、foo.bar.>を購読している場合、foo.bar.baz.quxも受け取ることができます。 サンプルコードとして、次のようなものを書いてみました。 Publisher 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package main import ( "log" nats "github.com/nats-io/go-nats" ) func main() { nc, err := nats.Connect("localhost:4222") if err != nil { log.Fatal(err) } defer nc.Close() if err := nc.Publish("subjectFoo", []byte("bodyBar")); err != nil { log.Fatal(err) } } Subscriber 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( "log" nats "github.com/nats-io/go-nats" ) func main() { nc, err := nats.Connect("localhost:4222") if err != nil { log.Fatal(err) } defer nc.Close() sub, err := nc.Subscribe("subjectFoo", callback) if err != nil { log.Fatal(err) } log.Printf("Subject: %s", sub.Subject) log.Printf("Queue: %s", sub.Queue) ch := make(chan struct{}) <-ch } func callback(message *nats.Msg) { log.Print(string(message.Data)) } それぞれ、適当なファイルに保存し、go runで起動します。あらかじめSubscriber側を起動しておくことで、Publisherを起動した際にメッセージ(今回は"bodyBar")が(Subscriber側で)Printされるはずです。 ポイントは*nats.Conn.Subscribeが非同期な関数で、メッセージを受け取った際にcallback関数が呼ばれる、というところです。 今回のサンプル中では<-chとしてブロックしていますが、何らかの方法でブロックしないと、受け取る前にmainが終わってしまうので注意が必要です。 同期処理したい場合には、*nats.Conn.SubscribeSyncを使用することで次のように書き換えられます。 ...

2019-02-06 · nasa9084