20190226のGoに関する記事は12件です。

GORM で Null アップデート

はじめに

Go の O/R マッパーである GORM を使っていて、NULL でのアップデートに困っていたので記載しておきます。

環境

  • Go 1.11.5
  • jinzhu/gorm v1.9.2

結論

以下の User モデルを更新する例です。

type User struct {
    ID   uint `gorm:"primary_key"`
    Age  int
    Name string
}

var user User
db.First(&user)

// 1 カラムの NULL アップデート
db.Model(&user).Update("name", gorm.Expr("NULL"))
// 実行 SQL : UPDATE `users` SET `name` = NULL  WHERE `users`.`id` = '1'

// 複数カラムの NULL アップデート
db.Model(&user).Updates(map[string]interface{}{
    "age":           18,
    "name":          gorm.Expr("NULL"),
})
// 実行 SQL : UPDATE `users` SET `age` = '18', `name` = NULL  WHERE `users`.`id` = '1'

SQL Expression によるアップデートに使用する gorm.Expr を使用して、直接 NULL を渡すことで NULL でアップデートができることが分かりました。

試行錯誤

通常のアップデートであれば以下のように指定することで可能ですが、NULL でアップデートするには nil を渡すなどで上手くいきませんでした。

db.Model(&user).Updates(User{Age: 18, Name: "hello"})

nil でのアップデート

NULL を含んだ Insert 文を実行するには、対象のカラムをポインタにして nil を渡すことで作成されます。

type User struct {
    ID   uint `gorm:"primary_key"`
    Age  int
-   Name string
+   Name *string
}

実際に実行してみると、NULL で作成されていることが分かります。

user := User{ID: 1, Age: 18, Name: nil}
db.Create(&user)
// 実行 SQL : INSERT INTO `users` (`id`,`age`,`name`) VALUES ('1','18',NULL)

では、同じ要領でアップデートでもポインタにしたカラムに nil を渡してみると、無視されてしまうことが分かりました。

var user User
db.First(&user)

db.Model(&user).Updates(User{Age: 18, Name: nil})
// 実行 SQL : UPDATE `users` SET `age` = '18'  WHERE `users`.`id` = '1'

GORM のドキュメントを見ると以下のように書いてあったので、構造体で実行すると無視されてしまうようです。

When query with struct, GORM will only query with those fields has non-zero value, that means if your field's value is 0, '', false or other zero values, it won't be used to build query conditions,

参考

How Do I Set a Field To NULL?
GORM

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

Docker Registryを構築する

はじめに

本記事は、Docker Registryを使用して、プライベートレジストリ環境を構築する手順について記載しています。

本記事の環境は以下になります。
手元のMacBook Airがクライアントで、iMacがプライベートレジストリ環境になります。

Docker.png

Docker Registry

Docker Registryは、Dockerイメージを一元管理するためのリポジトリサービスです。
公式のDocker Hubでは、CentOSなど各種Linuxディストリビューションの公式イメージや、有志で作成されたイメージなどが配布されています。

公式イメージの「registry」を使用することで、プライベートレジストリ環境を構築することができます。

現在、registryはVersion2.0系が主流で、Go言語で実装されています。

Docker Registryの構築

イメージのダウンロード(registry)

はじめに、registryイメージをダウンロードします。

  • ダウンロードするイメージの確認
    docker search registry
NAME                                    DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
registry                                The Docker Registry 2.0 implementation for s…   2454                [OK]                
konradkleine/docker-registry-frontend   Browse and modify your Docker registry in a …   213                                     [OK]
hyper/docker-registry-web               Web UI, authentication service and event rec…   161                                     [OK]
atcol/docker-registry-ui                A web UI for easy private/local Docker Regis…   113                                     [OK]
distribution/registry                   WARNING: NOT the registry official image!!! …   57                                      [OK]
marvambass/nginx-registry-proxy         Docker Registry Reverse Proxy with Basic Aut…   44                                      [OK]
jhipster/jhipster-registry              JHipster Registry, based on Netflix Eureka a…   41                                      [OK]
google/docker-registry                  Docker Registry w/ Google Cloud Storage driv…   37                                      
confluentinc/cp-schema-registry         Official Confluent Docker Images for Schema …   29                                      
joxit/docker-registry-ui                Docker registry v2 web User Interface           23                                      [OK]
klausmeyer/docker-registry-browser      Web Interface for the Docker Registry HTTP A…   17                                      [OK]
openshift/origin-docker-registry        The integrated OpenShift V3 registry            13                                      
deis/registry                           Docker image registry for the Deis open sour…   12                                      
landoop/schema-registry-ui              UI for Confluent's Schema Registry              7                                       [OK]
parabuzzle/docker-registry-ui           Docker registry frontend for registry v2        6                                       
quiq/docker-registry-ui                 Docker Registry UI                              5                                       
anoxis/registry-cli                     You can list and delete tags from your priva…   5                                       [OK]
elasticio/docker-registry-ecs           Docker image to run Docker private registry …   4                                       [OK]
allingeek/registry                      A specialization of registry:2 configured fo…   4                                       [OK]
webhippie/registry                      Docker images for registry                      1                                       [OK]
aibaars/docker-registry2-gcs            Docker Registry2 w/ Google Cloud Storage dri…   1                                       
yammer/docker-registry-cache            Simple docker registry cache using squid-pro…   1                                       [OK]
convox/registry                                                                         0                                       
lorieri/registry-ceph                   Ceph Rados Gateway (and any other S3 compati…   0                                       
torchbox/kube-registry-proxy            kube-registry-proxy fork with correct timeou…   0                                       [OK]
  • イメージのダウンロード
    docker pull registry
Using default tag: latest
latest: Pulling from library/registry
169185f82c45: Pull complete 
046e2d030894: Pull complete 
188836fddeeb: Pull complete 
832744537747: Pull complete 
7ceea07e80be: Pull complete 
Digest: sha256:870474507964d8e7d8c3b53bcfa738e3356d2747a42adad26d0d81ef4479eb1b
Status: Downloaded newer image for registry:latest
  • イメージの確認
    docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            latest              d0eed8dad114        3 weeks ago         25.8MB
centos              latest              1e1148e4cc2c        2 months ago        202MB

コンテナの起動

次に、ダウンロードしたregistryイメージをもとにレジストリ用のコンテナを起動します。
レジストリは5000番ポートを使用します。

  • コンテナの起動
    docker run -d -p 5000:5000 registry
e4a6f960fdb3c79250a4baf90e84c96bcebedd86c023900bb3786f32828ae0b8
  • コンテナの起動確認
    docker ps --format "{{.ID}}\t{{.Image}}\t{{.Ports}}"
e4a6f960fdb3    registry    0.0.0.0:5000->5000/tcp

これでプライベートレジストリ環境が構築できました。

イメージのアップロード

構築したレジストリ環境にアップロードするためのイメージを作成します。
(※)本記事の環境はMac

本記事では例として、CentOSののベースイメージにnginxをインストールします。
なお、nginxをインストールするためには、nginx用レポジトリの追加が必要になるため、公式を参考にして、レポジトリファイルの内容をコピーして作成します。

  • Dockerfileの作成
    vi Dockerfile
# イメージの取得
FROM centos:latest

# 作成者情報
MAINTAINER 0.1 https://twitter.com/Brutus08159681

# Nginx用レポジトリファイルのコピー
ADD nginx.repo /etc/yum.repos.d/

# Nginxのインストール
RUN ["yum", "-y", "install", "nginx"]

# Nginxの自動起動設定
RUN ["systemctl", "enable", "nginx.service"]
  • nginx.repoの作成
    vi nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
  • Dockerファイルのビルド
    docker build -t webserver .
Sending build context to Docker daemon  10.75kB
Step 1/5 : FROM centos:latest
 ---> 1e1148e4cc2c
Step 2/5 : MAINTAINER 0.1 https://twitter.com/Brutus08159681
 ---> Running in 4d7ff271e184
Removing intermediate container 4d7ff271e184
 ---> 360f621826fd
Step 3/5 : ADD nginx.repo /etc/yum.repos.d/
 ---> 065d346e93ad
Step 4/5 : RUN ["yum", "-y", "install", "nginx"]
 ---> Running in db772f205c5a
Loaded plugins: fastestmirror, ovl

//途中省略

Installed:
  nginx.x86_64 1:1.14.2-1.el7_4.ngx                                             

Dependency Installed:
  make.x86_64 1:3.82-23.el7            openssl.x86_64 1:1.0.2k-16.el7           

Complete!
Removing intermediate container db772f205c5a
 ---> a5dc224331c9
Step 5/5 : RUN ["systemctl", "enable", "nginx.service"]
 ---> Running in cb98e1b1ec8a
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service, pointing to /usr/lib/systemd/system/nginx.service.
Removing intermediate container cb98e1b1ec8a
 ---> afd46670157f
Successfully built afd46670157f
Successfully tagged webserver:latest

プライベートレジストリにアップロードするために、先ほど作成したイメージにタグを付けます。

  • タグ付け
    docker tag webserver localhost:5000/nginx
    docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
webserver              latest              bc4ced76a58a        2 minutes ago       284MB
localhost:5000/nginx   latest              bc4ced76a58a        2 minutes ago       284MB
registry               latest              d0eed8dad114        3 weeks ago         25.8MB
centos                 latest              1e1148e4cc2c        2 months ago        202MB

タグ付けしたイメージをプライベートレジストリ環境にアップロードします。

  • イメージのアップロード(コンテナのプライベートレジストリ)
    docker push localhost:5000/nginx
The push refers to repository [localhost:5000/nginx]
eecb714a1eec: Pushed 
f12d083c2a5d: Pushed 
fbf94d353010: Pushed 
071d8bd76517: Layer already exists 
latest: digest: sha256:ec84796f9312457e9afd0a0b3fff1ee40e52512f4f3b83dbaee8a7e728971ee4 size: 1155

イメージのアップロードが完了したので、確認のため、ローカルに保存したイメージを削除します。

docker rmi webserver

Untagged: webserver:latest

docker rmi localhost:5000/nginx

Untagged: localhost:5000/nginx:latest
Untagged: localhost:5000/nginx@sha256:ec84796f9312457e9afd0a0b3fff1ee40e52512f4f3b83dbaee8a7e728971ee4
Deleted: sha256:afd46670157fa5608642dae40aaad502eb69787bb0494536d9774089f7e44d8d
Deleted: sha256:edfe82b3e83ea6e3fbcb29ba5d801f7a00d5cc6cd1c650fe519d00fbea6a34f7
Deleted: sha256:a5dc224331c9be59615fdf8101d188ed56dd6052176c744f07d1bbdb23109464
Deleted: sha256:313c7c3089ee43faf482383dcb1e04e35cef648c566332239a86920fb4921d1e
Deleted: sha256:065d346e93ad13542b6790324f0ca7766335730334d0d39200948239737164a6
Deleted: sha256:d24e00ece851f250e549c84f3e2165f952cee2bf43c913dd1328023e8888bb81
Deleted: sha256:360f621826fd1ad8823e54e70a586a37cbdea1c9844b6b9f7b00853537931548

docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            latest              d0eed8dad114        3 weeks ago         25.8MB
centos              latest              1e1148e4cc2c        2 months ago        202MB

イメージのダウンロード(プライベートレジストリ環境→nginxのイメージ)

プライベートレジストリ環境にあるレジストリ用のコンテナから、先ほどアップロードしたnginxのイメージをダウンロードします。

  • コンテナのプライベートレジストリ環境からダウンロード
    docker pull localhost:5000/nginx
latest: Pulling from nginx
a02a4930cb5d: Already exists 
53cacbc24ea9: Pull complete 
6743d7a92897: Pull complete 
0b2d1befb17c: Pull complete 
Digest: sha256:ec84796f9312457e9afd0a0b3fff1ee40e52512f4f3b83dbaee8a7e728971ee4
Status: Downloaded newer image for localhost:5000/nginx:latest
  • イメージの確認
    docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
localhost:5000/nginx   latest              afd46670157f        3 minutes ago       284MB
registry               latest              d0eed8dad114        3 weeks ago         25.8MB
centos                 latest              1e1148e4cc2c        2 months ago        202MB
  • コンテナの起動
    docker run --privileged --name "test1" -d -p 8080:80 localhost:5000/nginx /sbin/init
  • nginxの起動確認
    ブラウザからローカルホストの8080にアクセスします。 スクリーンショット 2019-02-23 20.40.35.png

「Welcome to nginx!」の画面が表示されればOKです。

他のDockerクライアントからのダウンロード

他のDockerがインストールされているクライアントから、プライベートレジストリ環境にあるイメージをダウンロードする手順について解説します。

プライベートレジストリ環境のイメージをダウンロードする場合は、docker pullコマンドの引数にプライベートレジストリ環境のIP:5000/取得イメージ名を指定するだけですが、ホスト側で証明書の設定をしていないと以下のようにエラーが出力されます。

docker pull プライベートレジストリ環境のIP:5000/nginx

Using default tag: latest
Error response from daemon: Get https://プライベートレジストリ環境のIPアドレス:5000/v2/: http: server gave HTTP response to HTTPS client

ホスト側で証明書を使用しないでHTTP接続する場合は、以下の設定を行います。
(※)本記事の環境はMac

クライアン側のDockerアイコンをクリックして、perferencesのDeamonタブを開きます。
スクリーンショット 2019-02-23 21.08.24.png

insecure-registries:の「+」をクリックし、プライベートレジストリ環境のIP:5000を追加します。最後に、「Apply & Restart」をクリックします。

スクリーンショット 2019-02-23 21.15.36.png

Docker再起動後、再度、docker pullコマンドを実行すると、イメージがダウンロードできます。

Using default tag: latest
latest: Pulling from nginx
a02a4930cb5d: Pull complete 
53cacbc24ea9: Pull complete 
6743d7a92897: Pull complete 
0b2d1befb17c: Pull complete 
Digest: sha256:ec84796f9312457e9afd0a0b3fff1ee40e52512f4f3b83dbaee8a7e728971ee4
Status: Downloaded newer image for プライベートレジストリ環境のIPアドレス:5000/nginx:latest

おわりに

プライベートレジストリ環境を構築することで、統一したイメージを使用した開発ができるので、Dockerの持ち味をより引き出せます。

参考

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

WordPress 4.7 REST APIの時刻フォーマットがイケてないのでGolangで苦労した話

概要

  • wordpress 4.7にはREST APIで記事の一覧や内容を取得できる機能があります
  • これを利用してgolangで記事の一覧を取得するクライアントを作成したが時刻のパースでエラーが発生します
  • 何故かとおもったらAPIの返す時刻のフォーマットがRFC3339「もどき」でGoがパース不可能になっていました

wordpress4.7のREST APIについて

こちらのページのjsonを引用させてもらうと以下のようになります

[
  {
    "id": 14687,
    "date": "2017-01-12T12:00:30",
    "date_gmt": "2017-01-12T03:00:30",
    "guid": {
      "rendered": "http://loumo.jp/?p=14687"
    },
    "modified": "2017-01-07T19:13:26",
    "modified_gmt": "2017-01-07T10:13:26",

Goでパースできない

上記のようなjsonをパースするときGoでは以下のようにstructを宣言してパースを行いますが

type News struct {
    Date    time.Time `json:"date"`
    DateGmt time.Time `json:"date_gmt"`
}

var newsResponse []*News
decoder := json.NewDecoder(res.Body) // APIから取得してきます
    if err := decoder.Decode(&newsResponse); err != nil {
        fmt.Println(err)
        return nil, err
    }

このときこのようなエラーが出ます

parsing time ""2019-02-26T02:04:34"" as ""2006-01-02T15:04:05Z07:00"": cannot parse """ as "Z07:00"

なぜパースができないのか

  • GoはRFC3339に則ってパースしようとしますが、Wordpressの吐いている時刻のフォーマットが正しいRFC3339ではないことが原因のようです
  • "date": "2017-01-12T12:00:30"
  • 正しくは "2017-01-12T12:00:30Z" または "2017-01-12T12:00:30+0900" などでなければいけません
  • ということでWordpressのREST APIの時刻をGolangで扱うときは気をつけましょう....
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Udemy】gRPC [Golang] Master Class: Build Modern API & Microservicesをやってみた

はじめに

  • 年末にUdemyのコースがセールになってまして、Go言語使っててHTTP/2やmicroserviceが学べるコースがないかなと探してたらこれが見つかり買ってみました。
    gRPC [Golang] Master Class: Build Modern API & Microservices
  • でもすぐにやらなくて積んdemyなってました。。。
  • なので【Qiita×Udemy特別企画】学習応援イベントがやってたので試しにのっかってみました。

コース概要

の前に

英語

このコースは中身をみればわかると思いますが、全部英語です。
ですが大して難しい英語を使ってるわけでもありませんし、プログラミングですからソースコード見て理解すればいいので英語がハードルになることはそんなに無いかと思います。
自分はまあまあ英語が得意なんで苦手な方はいくらか差し引いて考えた方がいいかも。

概要

ざっとこんな感じでした。

  • gRPC オーバービュー
  • gRPC 詳細
  • gRPC の各種実装パターン
    • Unary
    • Server Streaming
    • Client Streaming
    • Bi-Directional Streaming
  • その他機能の実装
    • エラー実装
    • デッドライン
    • SSLセキュリティ
    • 別言語からの通信(Java)
    • gRPC のEvans クライアントの使い方
  • 実践的なアプリを作ってみる
  • MongoDBと連携してCRUD APIを実装
  • おまけ(クーポンとか)

gRPC の各種実装パターンでそれぞれUnary、Server Streaming、Client Streaming、Bi-Directional Streamingを説明していて、そこについてのプログラミング解説が主な内容でした。

やったこと

ひたすら写経!Webページを作った時のように見栄えの良いものを作るわけでもないので、かっこいいアウトプットはありません。
リポジトリを載せておきます。
https://github.com/suganoo/go-grpc-course

gRPC のサーバーを起動して、クライアントを実行してみるのをひたすらやってました。

学んだこと

ざっくり各種パターン

Unaryパターン

単純にリクエストを投げて、レスポンスを受けるというパターンです。(1:1)
REST APIみたいなものでした。ex. 2つの数字を送ったら合計値が返される。

Server Streaming

1回リクエストを投げて、複数のレスポンスを受けるパターンです。(1:n)
ex. ある数値を送ったら素数がすべて返ってくる。

Client Streaming

複数回リクエストを投げたら、1回のレスポンスを受けるパターンです。(n:1)
ex. 複数個の数値を送ったら、平均値が返される。

Bi-Directional Streaming

相互に複数回通信するパターンです。(n:n)
ex. 複数個の数値を用意しておき、順次1コづつ数値を送ると、それまでに受けた数値の最大値を更新した時に最大値を返す。

Calculator

上で例として書いてますが、平均値を返したり、最大値を返したりするアプリを作ってみました。なるほどという感じです。

MongoDBと連携してアプリ

ブログを作ったと想定して、MongoDBにデータを入れこみCRUDを実装しました。MongoDBは実際使うのは初めてで、これはなかなかMongoDBも含めて勉強になりました。便利だねー

Evans CLI

gRPCのサーバーに対してのクライアントアプリです。最後にちょろっと出てきてこいつは便利だなー!と思いました。でもDLしてビルドしたところなぜか接続できず残念でした。。。
https://github.com/ktr0731/evans

別言語からの通信(Java)

これは動画からしか見てませんが、他の言語からちゃんと通信できてて便利でした。

MongoDBのドライバ設定はちょっと注意

バージョンが違うのかimportの定義の仕方は下記のような書き方でないとエラーになってました。


"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"

時間があればやりたかったこと

Udemy特別企画の締め切りを目標にしてて、さらに始めるのが2月半ばを過ぎてからだったので、コースを終わらすことしかできませんでした。
コーディングしてるといろいろとやってみたいことが出てきたので、それが今後の宿題かなと思います。

  • HTTP/2通信としてはどうなってるのか。 サーバーはnet.Listen(...)で始めてるし、クライアントはgrpc.Dial(...)で始めてたのでヘッダーとかどうなってるのか。
  • protoc で生成されるGoのソースを見てみる。
  • TLS通信の仕方。各種サーティフィケーションファイルはコマンドコピペだったのでちゃんと復習したい。
  • gRPC 使ってチャットアプリ作ってみたいですね。

最後

やっぱこういう企画がないとやらないですね(汗)。
ですが今回を気にgRPCが学べたのでとてもよかったです。
またUdemyは年末年始になるとセールになるのでお得です。
Udemyだと新しい技術が出てきたときに英語版であればいち早くコースが出てくるのでチェックしてみるといいかと思います。

ザーッとやってしまったとはいえ修了証明書が最後にでるとうれしいものですね。

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

Go1.12リリースしたのにまだReplace()に-1渡してるの?静的解析でReplaceAllに切り替えれるコードを検知する静的解析ツールを作った

Go1.12がリリースしました!!尊い!!尊い!!:tada: :tada: :tada:

Go1.12 から入る ReplaceAllメソッドはご存知でしょうか。リリース記念に ReplaceAll に変更可能なコードを検知する簡単な静的解析ツールを作ったので、ReplaceAllの紹介 & ツールの紹介も兼ねてどうやって作ったのかも軽く紹介します。

そもそも ReplaceAll って ??

今まで指定した文字列やbyte列をreplaceする際は下記のようにReplaceメソッドを使っていました。

// stringsパッケージの場合
func Replace(s, old, new string, n int) string

n<0の値を渡せばoldで指定した箇所全てをnewに変換できます。しかし、Go1.12からReplaceAllが使えます。

// stringsパッケージの場合
func ReplaceAll(s, old, new string) string

replaceAll は bytes と strings パッケージに搭載されてます。Replaceに-1とか渡さなくともこっち使えば良いですね。

作った静的解析ツール

こちらです chkreplace と言います。

インストールして

$ go get github.com/po3rin/chkreplace/cmd/chkreplace

使う

$ chkreplace github.com/po3rin/chkreplace/example

example/replace.go:11:17: detect strings.Replace use negative value -1
example/replace.go:23:15: detect bytes.Replace use negative value -1

検知できてます。

どうやって作ったのか

最近流行りの analysisパッケージで作ってます。Goで静的解析ツールを作成するための仕組みを提供するパッケージです。
https://godoc.org/golang.org/x/tools/go/analysis

出だしは @tenntenn さん作の skeleten という静的解析ツールのテンプレートを生成してくれるツールを使いました。雛形も作ってくれるので、そのまま静的解析の作業に入れます。

analysisパッケージについての解説は強強な方達が詳しい解説を書いているのでそちらに譲ります。analysisパッケージを触ったことない方はこちらの記事が最高に勉強になります。

analysisパッケージの基本的な使い方の記事
https://budougumi0617.github.io/2019/02/01/how-to-use-analisys-package/

実際にfindcallというパッケージの実装を見ていく記事
https://tech.mercari.com/entry/2018/12/16/150000

実際のコードを少し解説

こちらが今回作ったchkreplaceの主要コード箇所です。skeletenから生成したinspect.Preorderに実際の処理を書いていきます。ゴリ押し感が否めない。。コメントで軽く解説します。

inspect.Preorder(nodeFilter, func(n ast.Node) {
        // ast.CallExpr という 関数またはメソッド呼び出しに関する構文木のノードをcallとして所得
        if call, ok := n.(*ast.CallExpr); ok {
            var id *ast.SelectorExpr
            var idx *ast.Ident

            switch fun := call.Fun.(type) {
            case *ast.SelectorExpr:
                id = fun
            default:
                return
            }

            switch x := id.X.(type) {
            case *ast.Ident:
                idx = x
            default:
                return
            }

            if id != nil && idx != nil && !pass.TypesInfo.Types[id].IsType() {      
                // strings.Replace もしくは bytes.Replace であることを確認
                if id.Sel.Name == "Replace" && (idx.Name == "strings" || idx.Name == "bytes") {
                    var expr *ast.UnaryExpr

                    // 念の為引数の数が4つであることを確認
                    if len(call.Args) != 4 {
                        return
                    }

                    // 4つ目の引数の構文木ノードを所得
                    switch arg := call.Args[3].(type) {
                    case *ast.UnaryExpr:
                        expr = arg
                    default:
                        return
                    }

                    switch a := expr.X.(type) {
                    case *ast.BasicLit:
                        // 引数がintかつマイナスの値かどうかをチェック。
                        // tokenパッケージに定義されているものものと付き合わせます
                        if a.Kind == token.INT && expr.Op == token.SUB {
                            // 検知結果をファイルパス、行数と一緒に出力!!!! 
                            pass.Reportf(call.Lparen, "detect %s.%s use negative value -%s", idx.Name, id.Sel.Name, a.Value)
                        }
                    }
                }
            }
        }
    })

今回は findcall パッケージの実装を大いに参考にしています。
https://github.com/golang/tools/tree/master/go/analysis/passes/findcall

実際に構文解析するフェーズにおいてはGoのASTの知識が必要です。全てのノードを知っておくなんてのは無理なので、最初はast.PrintなどでテストケースのASTを確認しておくと良いでしょう。僕はそこからゴリゴリとswitch文で書いていきました。構文解析においては下記が良解説すぎるので一読をお勧めします。
https://motemen.github.io/go-for-go-book/

テストの書き方

先ほど上に記載したコードの出力部分はこうなっています。

pass.Reportf(call.Lparen, "detect %s.%s use negative value -%s", idx.Name, id.Sel.Name, a.Value)

skeltonで生成したtestdata/src/a/a.goに解析対象のコードを書き、解析時に出力される文字列をwantをつけてコメントとして書いておけばテストは完成です。つよ!!

func stringsReplaceUseNegative() {
    text := "aaabbbcccaaabbbccc"
    strings.Replace(text, "aaa", "eee", -1) // want "detect strings.Replace use negative value -1"
    fmt.Println(text)
}

func stringsReplacePositive() {
    text := "aaabbbcccaaabbbccc"
    strings.Replace(text, "aaa", "eee", 2)
    fmt.Println(text)
}

func bytesReplaceUseNegative() {
    b := []byte("aaabbbcccaaabbbccc")
    bytes.Replace(b, []byte("aaa"), []byte("eee"), -1) // want "detect bytes.Replace use negative value -1"
    fmt.Println(b)
}

func bytesReplacePositive() {
    b := []byte("aaabbbcccaaabbbccc")
    bytes.Replace(b, []byte("aaa"), []byte("eee"), 3)
    fmt.Println(b)
}

注意点

chkreplace はまだ strings や bytes という文字列ではないと検知しないので下記のようなimportをしていると検知できません。。

import (
   s "strings"
)

その為、パッケージを解析する必要がありますが、力尽きました。。。すいません。。

まとめ

analysisパッケージのおかげで簡単に静的解析ツールを作成できました。初めて作ったのでゴリゴリ感が否めないですが、静的解析は楽しかったです。もっといい書き方あるよ!ってのがあれば教えてください!!

ちなみにこの画像は https://qiita.com/po3rin/items/eac851304cf058c532af で紹介したツールを使って生成してます。ぜひ投稿する際に使ってみてください!!

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

Go の http request 中に発生した本来のエラーを取得する!

例えばこんな感じのコードを皆さんはよく書くはずです。

ctx, cancel := context.WithCancel(context.Background())
req, _ := http.NewRequest("GET", url, nil)
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
    return err
}

今回はこの return err で受け取った error の値が下記のようになる事がありました。

Get https://example.com: context canceled

どうしても context canceled のエラーだけを取得して、何かしらのアクションをしたかったのでずっとこんなコードを書いてました。

func errCheck(err error) error {
    msg := err.Error()
    if strings.Contains(msg, context.Canceled.Error()) {
        // do something
        return nil
    }
    return err
}

しかし上記のコードはなんか気持ち悪かったので、http パッケージのソースコードを読んだ所、下記のようにハンドリングできることがわかりました。

func errorCheck(err error) error {
    if v, ok := err.(*url.Error); ok {
        err = v.Err
        if err == context.Canceled {
            // do something
            return nil
        }
    }
    return err
}

どうやらリクエスト内で発生したエラーは何故か *url.Error にラップされるらしいですよ!

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

GolangのsqlxでIN条件を扱う

golangにはsqlxというdatabase/sqlの拡張機能を提供するライブラリがあります。

SQLを書いていると、WHERE句にINという条件を使いたくなることがありますが、sqlxでは普通のやり方ではこのINが利用できません。

sql := `SELECT * FROM users WHERE id IN (?)`

var users []User
if err := sqlx.Select(&users, sql, []int{1,2,3,4,5}); err != nil {
    // これはエラーになる
}

sqlxはこの問題を解決するために、In(query string, args ...interface{}) (string, []interface{}, error)というヘルパ関数を提供しています。
この関数を利用することでIN条件を持つクエリを書くことが出来るようになります。

sql := `SELECT * FROM users WHERE id IN (?)`

sql, params, err := sqlx.In(sql, []int{1,2,3,4,5})
if err != nil {
    log.Fatal(err)
}

// sql, paramsは以下のような形で返され、有効なクエリを実行することが出来るようになります。
//
// sql: "SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?)"
// params: [1 2 3 4 5]

var users []User
if err := sqlx.Select(&users, sql, params...); err != nil {
    log.Fatal(err)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GolanのsqlxでIN条件を扱う

golangにはsqlxというdatabase/sqlの拡張機能を提供するライブラリがあります。

SQLを書いていると、WHERE句にINという条件を使いたくなることがありますが、sqlxでは普通のやり方ではこのINが利用できません。

sql := `SELECT * FROM users WHERE id IN (?)`

var users []User
if err := sqlx.Select(&users, sql, []int{1,2,3,4,5}); err != nil {
    // これはエラーになる
}

sqlxはこの問題を解決するために、In(query string, args ...interface{}) (string, []interface{}, error)というヘルパ関数を提供しています。
この関数を利用することでIN条件を持つクエリを書くことが出来るようになります。

sql := `SELECT * FROM users WHERE id IN (?)`

sql, params, err := sqlx.In(sql, []int{1,2,3,4,5})
if err != nil {
    log.Fatal(err)
}

// sql, paramsは以下のような形で返され、有効なクエリを実行することが出来るようになります。
//
// sql: "SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?)"
// params: [1 2 3 4 5]

var users []User
if err := sqlx.Select(&users, sql, params...); err != nil {
    log.Fatal(err)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のHugoを使おうとして、ハマったので備忘録

HUGOとは?

静的なHTMLを作成することができるGo言語製の静的ページジェネレーターらしいです。

インストール環境

go version go1.11.4 linux/amd64

公式サイトを参考に

公式サイトに

mkdir $HOME/src
cd $HOME/src
git clone https://github.com/gohugoio/hugo.git
cd hugo
go install --tags extended

上記を試したのですが、依存関係のパッケージが無いと怒られました。(すみませんエラー残し特の忘れたので、多分パッケージ関連のエラーだったと思われます)

govendorを試す

govendor get github.com/gohugoio/hugo

を試したのですが、

github.com/gohugoio/hugo/vendor/github.com/cpuguy83/go-md2man/md2man
vendor/github.com/cpuguy83/go-md2man/md2man/roff.go:15:15: undefined: blackfriday.Extensions

というエラーに、
そのままググったところ、
https://github.com/gohugoio/hugo/issues/4390
こちらに行き着きました。

mageを使う

govendor ではなく、mage というものを使えとのことでしたので、

 go get github.com/magefile/mage 
 go get -d github.com/gohugoio/hugo 
 cd $HOME/go/src/github.com/gohugoio/hugo 
 mage vendor 
 mage install 

で、インストール出来ました。
なんかモヤモヤするけど。

何か、誤り等ありました。ご指摘いただけると幸いです。

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

Go言語を学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで②

このシリーズ目次

はじめに

こちらの記事は前回Go言語を学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで① - Qiitaの続きとなります。
Go言語を学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで②|Wano Group Developers Blogにあげたものと同じものになります。

しかしこの記事から読まれても全く支障がないので、お好きに読まれてください!

とりあえず文法が一通り抑えられたので、チームの先輩shunさんのアドバイスで、Goで簡単なCRUDアプリ(Web)作っていくことにしました。
とりあえず4段階くらいに分けるかあと思い、4ステップに分けて順次進めていきました。

②簡単なCRUDのWebアプリケーション作成

1.まずgoenvとdep導入

Gopkg.toml · dep
Golangをgoenvを使ってインストールしてみた - Qiita
goenvでgoをインストール 〜初心者向け〜 - Qiita
依存関係管理ツールdep(golang) - Qiita

goenvはgoのバージョン管理ツールで、depはパッケージ管理ツールです。どちらも私が配属されたVideo Kicksチームで採用しているということで使ってみました。
rubyでいうrbenvとBundlerということで、ここは問題なく導入。

2.echoでRESTAPI作る

【go】echoでapiサーバーを実装するときに最低限必要そうなことをまとめておく - とりあえずphpとか
Go の echo ってWebサーバーでサクッと REST しちゃう - Qiita

Echoというのは、Goでよく使われるフレームワークです。
うちのチームも採用しているようなので使ってみました。
API用途が多い、軽量のフレームワークになります。

だいたい公式ドキュメントEcho - High performance, minimalist Go web frameworkに使い方が書いてあるので、そこメインで参考にしました。わかりずらいところは上記サイト参照。
まずはJSON返す形で組んで動かして、echoの理解を深めました。

3.DB導入 Gorm使い方覚える

Mac へ MySQL を Homebrew でインストールする手順 - Qiita
GoでMySQLに接続する - Qiita
【GORM】Go言語でORM触ってみた - Qiita
GoのORマッパーでJoinデータのとり方を学ぶ! - イノベーション エンジニアブログ

Gormとは、Goでよく使われるORMです。
僕はフレームワークはRORしか使ったことがなかったので、別でORMを使っていくのは初めてでした。
でも結構使い安くて、公式ドキュメントGORM - The fantastic ORM library for Golang, aims to be developer friendly.もわかりやすいのである程度基本的なお作法はすぐ抑えやすいと思います。
前の記事で言及したpreload問題などなど、ORMあるあるかもなやばいSQL発行してるみたいなところは、業務で使いながら適切なクエリチューニングができるようにしていきます。

4.CRUDアプリ作成

Go言語のWebフレームワーク「Echo」を使ってみる ①(Hello World的な) - Qiita
Go言語のWebフレームワーク「Echo」を使ってみる ②(リクエストパラメータの扱い) - Qiita
Go言語 - HTMLテンプレートの使い方 - 覚えたら書く
golang : template の range で index を使う - i++

1~3まで行えていれば、あとはwebのview側どうするか、ってとこだけだと思います。そこでechoのTemplates | Echo - High performance, minimalist Go web frameworkを利用して、renderingしていくのがメジャーのようです。
RORとかのフルスタックフレームワークにおんぶに抱っこだったのでわりかし使いづらさを感じましたが、こちらも公式リファレンスが一番わかりやすい気がします。その他細かいとこ(template内でのスライスの回し方とか)は上記のサイト参考にしながら進めました。

そうしてざっくり作ったのがこちらになります。
GitHub - takafk9/echo_crud

細かいエラーハンドリングとかログ管理とか一切してないです。とりあえずお試しで一通りいじってみた感じです。
突っ込みどころばかりだと思うのですが、0からとりあえず簡単な仕様を作ってみたい方向けということで。(許して)

Goはhtmlレスポンス拡張put,delete対応してないので、下記のサイトを参考に実装しようと思ったのですが、うまくいかず保留中。
[go,echo]formからputをリクエスト~methodOverRide~ - Qiita

@y_ussieさんの実装Go言語のWebフレームワーク「Echo」を使ってみる ②(リクエストパラメータの扱い) - Qiitaをかなり参考にしました。(大感謝)

複数画面ある場合どうtemplate扱おうかしら、って思ってたんですが、@y_ussieさんのmapにtemplate入れるの案良いな、と思って採用していました。

main.go@y_ussie

// Render はHTMLテンプレートにデータを埋め込んだ結果をWriterに書き込みます。
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return templates[name].ExecuteTemplate(w, "layout.html", data)
}

// 初期化を行います。
func init() {
    loadTemplates()
}

// 各HTMLテンプレートに共通レイアウトを適用した結果を保存します(初期化時に実行)。
func loadTemplates() {
    var baseTemplate = "templates/layout.html"
    templates = make(map[string]*template.Template)
    templates["hello"] = template.Must(
        template.ParseFiles(baseTemplate, "templates/hello.html"))
    templates["hello_form"] = template.Must(
        template.ParseFiles(baseTemplate, "templates/hello_form.html"))
}

ただ、initの処理で画面数だけtemplate初期化して用意しておく必要はないかなと思ったので、以下のようにgetTemplateメソッド作って、nameに対応したtemplateをBaseTemplateに都度マージして、返すようにしました。

main.go@fukubaka0825

// Render はHTMLテンプレートにデータを埋め込んだ結果をWriterに書き込みます。
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return getTemplate(name).ExecuteTemplate(w, "layout.html", data)
}

func getTemplate(name string)(*template.Template){
    switch name {
    case "posts_all":
        return template.Must(template.ParseFiles(_const.BaseTemplate, _const.PostsAll))
    case "update_post_form":
        return template.Must(template.ParseFiles(_const.BaseTemplate, _const.UpdatePost))
    default:
        return nil
    }
}


※2019/2/26 訂正
指摘があったため訂正です。

// template情報をキャッシュ
var templates = template.Must(template.ParseFiles("edit.html", "view.html"))    

templateキャッシュ【Go】 - 技術向上

上記の例のように、template.Mustでパース結果をキャッシュして、その結果をMapに詰めて
取り出しやすくするのは非常に理にかなっているので、@y_ussieさんの実装のままの方が良いみたいです。
ここで勉強できてよかった!


ディレクトリ分けとしてはオーソドックスなMVCな感じで分けました。

ただ今の時流でGoでコテコテのMVCって流石に、、、ってなってうちのPM(VPoE)に相談したところ、
「クリーンアーキテクチャで作り変えて見れば?軽くテストまで書いてみると恩恵も理解できて良いと思うよ」
とアドバイスをいただいたので、作り変えてみることに。
いやでもしかし、内心 「クリーンアーキテクチャって聞いたことあるけどなんぞ???」 って感じで、、、、、、、
ここから③に続きます。明日か明後日にあげれると思います。

ではまた次回!!!!

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

mercarigo スライドまとめ(個人用)

はじめに

参加してきて非常にためになったので個人的な復習用に

無い部分はまとめて頂けるようなので、後で追記します。

JavaとGoの比較

登壇者:@kentan
twitter: https://twitter.com/kentandx
スライド掲載なし

yamlをテストする

登壇者:@babarot
twitter: http://twitter.com/b4b4r07
Testing with YAML - Speaker Deck

shallow copyに気をつけろ

登壇者:@t-naritomi
twitter: https://twitter.com/ques0942
sliceのコピーで起きた不思議な話 - Google スライド

コンパイラ書いた話

登壇者:@kitasuke
twitter:https://twitter.com/kitasuke
Writing a compiler in go

コンパイラ書いた話(mapとheap)

登壇者:@dqneo
twitter: http://twitter.com/dqneo
Goのmapとheapを自作してみた / How to create your own map and heap in Go - Speaker Deck

ポメラニアン専用Goコンパイラ

登壇者:@tenntenn
twitter: http://twitter.com/tenntenn
ポメラニアン専用Goコンパイラ - Google スライド

Goのエラーハンドリング

登壇者:@morikuni
twitter: http://twitter.com/inukirom
mercari.go #6 - Google スライド

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

(時間がない人向け) プログラミング言語Go 1章 まとめ

はじめに

The Go Programming Language の1章を読んで、理解したこと、気になったことを時間がなくて読めない人向けにまとめます。 (日本語訳の書籍はこちら プログラミング言語Go )

第1章 チュートリアル

1.1 Hello World

  • Goはコンパイル言語。
  • Goのツールチェーンはソースプログラムとそのプログラムが依存しているものを機械語に翻訳。
  • サブコマンドの中で最も単純なものがrunでファイル名が.goで終わる1個以上のファイルをコンパイルし、ライブラリとリンクし、その結果さくせいされた実行可能ファイルを実行する。
  • Goのコードはパッケージで構成される。
  • 1つのパッケージは単一ディレクトリないの1個以上の.goファイルから構成される。
  • 各ソースはpackage宣言で始まる。
  • mainパッケージは特別で、ライブラリではなく単独で動作する実行可能なプログラムを定義。
  • mainパッケージ内ではmain関数も特別で、この関数からプログラムが実行される。
  • import文でどのパッケージが必要か伝える。
  • import宣言はpackage宣言のあとである必要がある。
  • 同じ行に2つ以上の文や宣言がないかぎり、セミコロン;は不要。
  • gofmtツールはコードを標準書式に書き換える。
  • goツールのfmtサブコマンドは指定されたパッケージの全てファイルや、デフォルトでは現在のディレクトリにある全てのファイルに対して、gofmtを適用。

1.2 コマンドライン引数

  • osパッケージはオペレーティングシステムを扱う数値と値を提供。
  • コマンドライン引数はArgsという変数で利用できる(os.Args)
  • Goではすべてのインデックスの指定は半開区間。
  • コメントは//からその行の終わりまで。
  • 慣習によりパッケージ宣言の直前のコメントにパッケージの説明を記述。
  • mainパッケージに対しては、プログラム全体を説明する1行か数行からなる完結した文を記述。
  • 変数が明示的に初期化されない時はゼロ値に初期化される。
  • :=記号は省略変数宣言の一部。
  • forループはGoで唯一のループ文。
  • Goは使われない変数を許さずコンパイルエラーになる。
    解決策は名前が_であるブランク識別子を使う。

1.3 重複した行を見つける

  • fmt.Printf 関数は複数の式のリストからフォーマットされた出力を生成。 Printfは verb (ヴァーブ, 動詞) と呼ぶこのような変換を10数個以上持っている。
  %d 10進表記の整数
  %x, %o , %b 16進表記, 8進表記, 2進表記
  %f, %g, %e 浮動小数点 : 3.141593 3.141592653589793 3.141593e+00
  %t  ブーリアン (true or false)
  %c  ルーン
  %s  文字列
  %q  引用符で囲まれた文字列"abc", あるいは、 ルーン 'c'
  %v  任意の値に対する自然なフォーマット
  %T  任意の値の型
  %%  パーセント記号そのもの
  • 文字列リテラルは、そのままでは不可視な文字列を表現するために、 タブ \t や 改行 \n のようなエスケープシーケンスを含むことができる。
  • 慣習的に f で名前が終わるフォーマット関数はfmt.Printfのフォーマット規則に従う。
    一方で、名前が ln で終わるフォーマット関数はPrintlnの規則に従い、
    引数が %v でフォーマットされ、その後に改行が続く。

1.4 GIFアニメーション

  • リサジュー図形 (Lissajous figure)
  • コンポジットリテラル。

1.5 URLからの取得

  • ネットワーク通信を行うパッケージはをnetの下にまとめている。
  • http.GetはHTTPリクエストを発行し、エラーがなければレスポンス構造体respで結果を返す。

1.6 URLからの並行な取得

  • Goの最も興味深く斬新な特徴は1つは並行プログラミングのサポート。
  • ゴルーチン(gorutine)は関数の並行した実行。
  • チャネル(channel)はゴルーチンが特定の型の値を他のゴルーチンへ渡すことを可能にする。
  • main関数は1つのゴルーチンで動作し、go文は新たなゴルーチンを生成。
  • make関数をつかってチャネルを作成。

1.7 ウェブサーバー

  • スラッシュで終わっているハンドラのパターンは、そのパターンを接頭辞として持つ全てのURLに一致。
  • .Lock()と.Unlock()を用いて、一度に一つのゴルーチンだけがその変数へ。 アクセスすることを保証し、Race condition(競合状態)を回避。
  • 関数リテラル = 使われる時点で定義される無名関数。

1.8 残りの項目

  • switch文のdefaultの位置はどこでも良い。
  • switch文のcaseはC言語のようにcaseは次のcaseへと通り抜けていくことはない。
    ほとんど使われないが、この動作を無効にできる、 faillthrough がある。
  • switch文ではオペランドを必要としない場合がある(ブーリアン式であるようなケース)
    この形式は タグなしswitch文(tagless switch)と呼ばれswitch trueと同じ。
  • switch文はforやifと同様に、省略可能な単文(省略変数宣言、インクリメント文、代入文、関数呼び出し)を含んでも良い。
  • 文にラベルをつけることができるので、breakとcontinueはそれらのラベルを参照することもできる。
  • goto文もあるが、これは自動生成されたコードで使われることを意図しており、プログラマが普通に使うことは意図していない。
  • type宣言で既存の型に対して名前をつけることができる。
  • Goは変数のアドレス値であるポインタを明示的に扱える。
  • &演算子は変数のアドレスを生成し、*演算子はポインタが参照している変数を取り出す。
    しかし、ポインタへの演算はない。
  • メソッドは名前付き型に関連づけられた関数。
  • インターフェースは異なる具象型(concreate type)を同じように取り扱うことを可能にしてくれる抽象型(abstract type)。その取り扱いは、具象型がどのような表現あるいは実装をされているかではなく、どのメソッドを持っているかに基づく。
  • コメントは // 以降か /* */に囲われた物。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む