- 投稿日:2020-07-07T20:06:58+09:00
Go: Starttls でメールの送信
こちらと同じことを Deno で行いました。
Python3: Starttls でメールの送信
hi-ho.ne.jp で試しました。hi-ho.go// --------------------------------------------------------------- // // hi-ho.go // // Jul/07/2020 // --------------------------------------------------------------- package main import ( "fmt" "os" "net/smtp" "github.com/joho/godotenv" ) func main() { fmt.Fprintf (os.Stderr,"*** 開始 ***\n") err := godotenv.Load(".env") if err != nil { panic(err) } server := os.Getenv("SERVER") port := os.Getenv("PORT") usr := os.Getenv("USR") password := os.Getenv("PASSWORD") fmt.Fprintf (os.Stderr,server + "\n") fmt.Fprintf (os.Stderr,port + "\n") from := os.Getenv("FROM") to := os.Getenv("TO") auth := smtp.PlainAuth("", usr, password, server) msg := []byte("" + "From: " + from + "\n" + "To: " + to + "\n" + "Subject: Test from Hi-ho PM 20:02\n" + "\n" + "テスト\n" + "今晩は。\n" + "PM 20:02\n" + "") err = smtp.SendMail(server + ":" + port, auth, from, []string{to}, msg) if err != nil { fmt.Fprintf(os.Stderr, "エラー: %v\n", err) return } fmt.Print("success\n") fmt.Fprintf (os.Stderr,"*** 終了 ***\n") } // ---------------------------------------------------------------.envSERVER = 'hi-ho.mose-mail.jp' PORT = 587 USR = '****@hi-ho.ne.jp' PASSWORD = '****' FROM = '****@hi-ho.ne.jp' TO = 'sample@example.com'実行結果
$ go run hi-ho.go *** 開始 *** hi-ho.mose-mail.jp 587 success *** 終了 ***
- 投稿日:2020-07-07T19:10:52+09:00
Golang 設定ファイルライブラリ 「Viper」 README翻訳 2020年7月
はじめに
spf13/viper: Go configuration with fangsの翻訳
すでに翻訳されている方[翻訳+α] Go言語の設定ファイルライブラリ Viper - Qiitaがいたので、それを元に現在のREADMEとの差分だけ付け足しました。
差分:https://github.com/spf13/viper/compare/30ce444..13df721
README_jp.mdとしてプルリク送りますが、マージされない可能性もあるのでQiitaとgistで公開しておきます。変更箇所は、利用プロジェクト紹介、INI、envfileのサポート、一部APIの変更、追加、リモートのこコード例の追加、くらいでした。
Viper Readme
Go言語の設定ファイルがついに牙を剥く!
多くのGoプロジェクトがViperを使って構築されています。
インストール
go get github.com/spf13/viper
Viperについて
Viperは、Twelve Factor appなどのGoアプリケーションにおける設定ファイル関連を一手に引き受ける万能ソリューションです。ライブラリとしてアプリケーション内での利用を想定しており、アプリケーションで設定ファイルのあらゆるニーズやファイル形式を扱えます。使える機能は以下のとおりです。
- デフォルト値の設定
- 設定ファイル(JSON、TOML、YAML、HCL、envfile)の読み込み
- 設定ファイルの変更監視と動的な再読み込み(オプション)
- 環境変数の読み込み
- リモートの設定ファイル(etcdやConsul)の読み込みと変更の監視
- コマンドラインフラグ(=いわゆる
-d
などのコマンドオプション)からの読み込み- バッファからの読み込み
- 明示的な値の設定
Viperはアプリケーションのあらゆる設定に必要な万能レジストリとみなすこともできます。
Viperを使う理由
いまどきアプリケーションを開発するうえで、設定ファイルをどんな形式にするかなどというレベルでいちいち悩むなんて馬鹿馬鹿しい限りです。足手まといかつ単調な設定作業はさっさと終わらせて、アプリそのものの開発に集中したい。Viperはそんなあなたのためのライブラリです。
Viperでは次の機能を実現できます。
- JSON/TOML/YAML/HCL/INI/envfile形式の設定ファイルの探索、読み込み、アンマーシャリングを行えます。
- 別の設定オプションで使用できるデフォルト値の設定メカニズムを提供します。
- コマンドラインフラグでオプションを指定して値をオーバーライドするメカニズムを提供します。
- 既存のパラメータを別名(エイリアス)を追加することで、パラメータを壊さずに名前を変更できます。
- 値がコマンドラインで提供されているのか、デフォルトとして設定ファイルから提供されているのかを明確に区別できます。
Viperの値は以下の優先順位で扱われます。上位の値は下位の値より優先度が高くなります。
Set
を明示的に呼び出す- フラグ(コマンドラインで与える)
- 環境変数(env)
- 設定(config)
- キー/バリュー ストア
- デフォルト値
重要Viperの設定キーでは大文字と小文字が区別しません。
これをオプションにすることについては、現在進行中のissueがあります。Viperに値を保存する
デフォルト値の設定
優れた設定値システムなら、デフォルト値をサポートしているものです。キーのデフォルト値は必須というわけではありませんが、キーが設定ファイル・環境変数・リモート設定ファイル・フラグで設定されなかった場合に備えて、デフォルト値を設定しておくと何かと便利です。
例:
viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})設定ファイルの読み取り
Viperでは、設定ファイルを探索するための最小限の設定を行わなければなりません。
JSON/TOML/YAML/HCL/INI/envfileファイルが設定ファイルとしてサポートされています。探索パスは複数指定できますが、現時点ではViperインスタンスひとつにつき設定ファイルひとつのみのサポートとなります。探索パスが複数ある場合、デフォルトの探索パスは固定されていないので、どの探索パスをデフォルトにするかをアプリケーション側から指定できます。
Viperで設定ファイルの探索と読み取りがどのように行われるかを例で示します。
設定ファイルの探索パスを必ず最低1つは指定しなければなりませんが、パスそのものはどこを指定しても構いません(特定のパスを指定しなければならないということはありません)。viper.SetConfigName("config") // 設定ファイル名を拡張子抜きで指定する viper.SetConfigType("yaml") // 設定ファイルの名前に拡張子がない場合に必要 viper.AddConfigPath("/etc/appname/") // 設定ファイルの探索パスを指定する viper.AddConfigPath("$HOME/.appname") // 探索パスを追加で指定する viper.AddConfigPath(".") // 現在のワーキングディレクトリを探索することもできる err := viper.ReadInConfig() // 設定ファイルを探索して読み取る if err != nil { // 設定ファイルの読み取りエラー対応 panic(fmt.Errorf("設定ファイル読み込みエラー: %s \n", err)) }設定ファイルが見つからない場合は以下のように対応します
if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // 設定ファイルが見つかりません。必要に応じてエラーを無視してください。 } else { // 設定ファイルは見つかりましたが、別のエラーが発生しました。 } } // 設定ファイルが見つかり、正常に解析されました注 [v1.6以降]: 拡張子のないファイルでも、プログラム的にフォーマットを指定できます。
.bashrc
のような拡張子を持たない設定ファイルをユーザのホームに置くこともできます。設定ファイルの書き込み
設定ファイルからの読み込みは便利ですが、実行時に行った変更をすべて保存しておきたい場合もあるでしょう。
そのために、いくつかのコマンドが用意されていて、それぞれに目的があります。
- WriteConfig - 現在のviperの設定を、定義済みのパスがあればそれに書き込みます。定義済みのパスがない場合はエラーになります。現在の設定ファイルが存在する場合は上書きされます。
- SafeWriteConfig - 現在のviper構成を事前に定義されたパスに書き込みます。事前に定義されたパスがない場合はエラーになります。現在の構成ファイルが存在する場合は、上書きされません。
- WriteConfigAs - 指定されたファイルパスに現在のviper構成を書き込みます。指定されたファイルが存在する場合、上書きされます。
- SafeWriteConfigAs - 指定されたファイルパスに現在のviper構成を書き込みます。指定されたファイルが存在する場合、上書きされません。
Safeが付いているものは、どのファイルも上書きしませんが、存在しない場合は作成します。デフォルトの動作は作成または上書きです。
例をあげます:
viper.WriteConfig() // 現在の設定を'viper.AddConfigPath()'と'viper.SetConfigName'で設定した定義済みのパスに書き込みます。 viper.SafeWriteConfig() viper.WriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.config") // これはすでに書かれているのでエラーになります。 viper.SafeWriteConfigAs("/path/to/my/.other_config")設定ファイルの更新検出と再読み込み
Viperはアプリケーションの実行中に設定ファイルの更新を自動検出して動的に読み込むことができます。設定変更を反映するためだけにいちいちアプリケーションを再起動する必要は、もうありません。
これを行うには、ViperのインスタンスからwatchConfigに監視を指定します。変更発生時に実行したい関数を渡すこともできます。
WatchConfig()
を呼び出す前に必ずconfigPathsをすべて追加しておいてくださいviper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("設定ファイルが変更されました:", e.Name) })io.Readerから設定ファイルを読み込む
Viperの設定値は、ファイル/環境変数/フラグ/リモートのキーバリューストアなどさまざまな方法で取得できます。しかも、必要であればViperに値を設定する方法を自由に実装することもできます。
viper.SetConfigType("yaml") // viper.SetConfigType("YAML")としてもよい // 以下の設定は一例であり、どのような方法でも構わない var yamlExample = []byte(` Hacker: true name: steve hobbies: - skateboarding - snowboarding - go clothing: jacket: leather trousers: denim age: 35 eyes : brown beard: true `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) viper.Get("name") // "steve"という値を得る設定のオーバーライド
オーバーライドは、コマンドラインフラグで指定することも、アプリケーションのロジックに基づいて行うこともできます。
viper.Set("Verbose", true) viper.Set("LogFile", LogFile)別名(エイリアス)の登録と利用
別名を指定することで、同じ値をさまざまなキーで参照できます。
viper.RegisterAlias("loud", "Verbose") viper.Set("verbose", true) // 下と同じ値になる viper.Set("loud", true) // 上と同じ値になる viper.GetBool("loud") // true viper.GetBool("verbose") // true環境変数を扱う
Viperは、Twelve Factor appの開発に欠かせない環境変数を完全にサポートしています。環境変数は以下の5とおりの方法で扱うことができます。
AutomaticEnv()
BindEnv(string...) : error
SetEnvPrefix(string)
SetEnvKeyReplacer(string...) *strings.Replacer
AllowEmptyEnv(bool)
※Viperでは環境変数の大文字と小文字が区別されますので、ご注意ください。
Viperには、環境変数が重複しないようにするためのメカニズムがあります。
SetEnvPrefix
を使用すると、読み込んだ環境変数にプレフィックスを追加できます。プレフィックスは、BindEnv
とAutomaticEnv
の両方で使用されます。
BindEnv
はパラメータを1つまたは2つ取ります。1番目はキー名、2番目は環境変数名です。環境変数名は大文字小文字を区別します。
環境変数名を渡さなかった場合は、Viperは自動的に環境変数が以下の形式にマッチしていると仮定します: prefix + "_" + すべて大文字のキー名。環境変数名(2番目のパラメータ)を明示的に指定した場合、自動的にプレフィックスは追加されません。たとえば2番目のパラメータがid
の場合、Viperは環境変数ID
を探します。Viperでは、環境変数はアクセスのたびに常に最新の値を読み取ります。
BindEnv
にアクセスした時点での値に固定されているわけではありません。
AutomaticEnv
は、SetEnvPrefix
と組み合わせることで力を発揮します。AutomaticEnv
を呼び出すと、以後viper.Get
リクエストが行われるたびにその時点の環境変数を実際にチェックします。Viperでチェックする環境変数は、キーをすべて大文字に変換した名前で探します。EnvPrefix
が設定されている場合は、探す環境変数名にプレフィックスを追加します。
SetEnvKeyReplacer
を実行すると、strings.Replacer
オブジェクトを使ってキー名を(ある程度)差し替えることができます。これは、たとえば環境変数で_
が区切り文字として使用されている状態で、Get()
呼び出しのキーで-
などの別の文字を使用したい場合に便利です。viper_test.go
のコードで実例を見ることができます。あるいは、
NewWithOptions
ファクトリ関数を使ってEnvKeyReplacer
を使うこともできます。
SetEnvKeyReplacer
とは異なり、StringReplacer
インターフェイスを受け入れるので、カスタムの文字列置換ロジックを書くことができます。
デフォルトでは、空の環境変数はセットされていないとみなされ、次の設定ソースにフォールバックします。空の環境変数をセットとして扱うには、AllowEmptyEnv
メソッドを使用します。環境変数の使用例
SetEnvPrefix("spf") // プレフィックス"spf"は自動的に大文字の"SPF"に変換される BindEnv("id") os.Setenv("SPF_ID", "13") // (実際は外部アプリで環境変数が設定されるのが普通) id := Get("id") // 値"13"を得るフラグを扱う
Viper ではコマンドラインフラグをバインドできます。特に、Cobraでも使用されている
Pflags
をサポートしています。
BindEnv
と同様に、バインドメソッドを呼び出した時点ではフラグの値は設定されず、実際にアクセスした時点で初めて設定されます。これにより、init()
関数のようなうんと初期の段階でも事前にバインドを行うことができます。個々のフラグでは、
BindPFlag()
メソッドは以下の機能を利用できます。例:
serverCmd.Flags().Int("port", 1138, "アプリケーション・サーバーのポート番号") viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))既存のpflagsのセットをバインドすることもできます (pflag.FlagSet)。
例:
pflag.Int("flagname", 1234, "help message for flagname") pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // pflag の代わりに viper から値を取得します。Viperでpflagを使用しても、標準ライブラリのflagなどのフラグ処理には影響しません。pflagパッケージでは、そうした別パッケージで定義されているフラグをインポートしたうえで扱っているからです。この機能は、pflagパッケージの
AddGoFlagSet()
という便利な関数を呼び出すことで利用できます。例:
package main import ( "flag" "github.com/spf13/pflag" ) func main() { // 標準ライブラリ"flag"を使います flag.Int("flagname", 1234, "help message for flagname") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // viperから値を取得します ... }フラグのインターフェイス
Pflags
を使用したくない方のために、他のフラグシステムとバインドできるGoインターフェイスが2種類用意されています。
FlagValue
は単一のフラグを表します。以下はこのインターフェイスのきわめてシンプルな実装例です。type myFlag struct {} func (f myFlag) HasChanged() bool { return false } func (f myFlag) Name() string { return "my-flag-name" } func (f myFlag) ValueString() string { return "my-flag-value" } func (f myFlag) ValueType() string { return "string" }自分のフラグをこのインターフェイスに実装したら、以下のようにViperにバインドします。
viper.BindFlagValue("my-flag-name", myFlag{})
FlagValueSet
は複数のフラグを扱います。以下はこのインターフェイスのきわめてシンプルな実装例です。type myFlagSet struct { flags []myFlag } func (f myFlagSet) VisitAll(fn func(FlagValue)) { for _, flag := range flags { fn(flag) } }自分のフラグをこのインターフェイスに実装したら、以下のようにViperにバインドします。
fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}}, } viper.BindFlagValues("my-flags", fSet)リモートのキー/バリューストアのサポート
Viperでリモートの設定を利用するには、以下のように
viper/remote
パッケージをブランク(_
)にインポートします。
import _ "github.com/spf13/viper/remote"
これにより、etcdやConsulなどのリモート・キー/バリューストアのパスにある設定ファイル(JSON/TOML/YAML/HCL/envfile)をViperで読み取れるようになります。取得した値はデフォルト値よりも優先されますが、ディスクから読み取った値・フラグ・環境変数をオーバーライドしません。
キー/バリューストアからの読み出しにはcryptというライブラリを使用できます。これにより、正しいGPGキーリングがあれば読み取り時の復号化と保存時の暗号化も自動的に行えるようになります。暗号化はオプションであり、必須ではありません。
リモート設定ファイルは、ローカル設定ファイルと組み合わせることも、ローカル設定から独立して使用することもできます。
crypt
ライブラリにはコマンドラインヘルパが用意されており、キー/バリューストアに値を保存するのに使用できます。crypt
は、デフォルトで http://127.0.0.1:4001 のetcdを使用します。$ go get github.com/bketelsen/crypt/bin/crypt $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json次のコマンドで、値が設定されたことを確認できます。
bash
$ crypt get -plaintext /config/hugo.json
値を暗号化して設定する方法の例や、Consulの使用方法については、
crypt
のドキュメントを参照してください。リモートのキー/バリューストアの例(暗号化なし)
etcd
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // バイトストリームにはファイル拡張子がないのでここで補う。サポートされている拡張子は "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" です。 err := viper.ReadRemoteConfig()Consul
You need to set a key to Consul key/value storage with JSON value containing your desired config.
For example, create a Consul key/value store keyMY_CONSUL_KEY
with value:
ご希望のコンフィグを含むJSON値でConsulのキー/バリューストレージにキーを設定する必要があります。
たとえばConsulのキー/バリューストアキーMY_CONSUL_KEY
を値を指定して作成します。{ "port": 8080, "hostname": "myhostname.com" }viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") viper.SetConfigType("json") // 明示的に json に設定する必要があります。 err := viper.ReadRemoteConfig() fmt.Println(viper.Get("port")) // 8080 fmt.Println(viper.Get("hostname")) // myhostname.comFirestore
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml" err := viper.ReadRemoteConfig()もちろん、
SecureRemoteProvider
も利用できます。リモートのキー/バリューストアの例(暗号化あり)
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") viper.SetConfigType("json") // バイトストリームにはファイル拡張子がないのでここで補う。サポートされている拡張子は "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" です。 err := viper.ReadRemoteConfig()etcdの変更を検出(暗号化なし)
// (参考)以下のようにviperのインスタンスを作成してもよい var runtime_viper = viper.New() runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") runtime_viper.SetConfigType("yaml") // バイトストリームにはファイル拡張子がないのでここで補う。サポートされている拡張子は "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" です。 // リモート設定の初回読み込み err := runtime_viper.ReadRemoteConfig() // 設定をアンマーシャリングする runtime_viper.Unmarshal(&runtime_conf) // open a goroutine to watch remote changes forever go func(){ for { time.Sleep(time.Second * 5) // リクエストの間隔を5秒おきに設定 // (現時点ではetcdのサポートのみテストしました) err := runtime_viper.WatchRemoteConfig() if err != nil { log.Errorf("unable to read remote config: %v", err) continue } // 新しい設定をアンマーシャリングして現在の設定の構造体に読み込む。 // システムの変更通知をチャネルで実装してもよい runtime_viper.Unmarshal(&runtime_conf) } }()Viperから値を取り出す
値の種類に応じて、以下のようなさまざまな関数やメソッドで値を読み出すことができます。
Get(key string) : interface{}
GetBool(key string) : bool
GetFloat64(key string) : float64
GetInt(key string) : int
GetIntSlice(key string) : []int
GetString(key string) : string
GetStringMap(key string) : map[string]interface{}
GetStringMapString(key string) : map[string]string
GetStringSlice(key string) : []string
GetTime(key string) : time.Time
GetDuration(key string) : time.Duration
IsSet(key string) : bool
AllSettings() : map[string]interface{}
Get系の関数はキーがない場合はゼロを返すことにご注意ください。キーがあるかどうかを確認するには
IsSet()
を使います。例:
viper.GetString("logfile") // ※setやgetのキーでは大文字小文字は区別されない if viper.GetBool("verbose") { fmt.Println("verbose enabled") }ネストしたキーにアクセスする
Viperのアクセサメソッドでは、ネストの深いところにあるキーを指すフォーマット済みパスを使用することもできます。たとえば以下のJSONファイルを読み込んだとします。
{ "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } }以下のように、ドット
.
で区切ったパスでネストを指定してフィールドにアクセスできます。GetString("datastore.metric.host") // ("127.0.0.1" を返す)この記法は、前述の優先順位ルールに従います。
パスの検索は、そのキーが見つかるまで以後の設定レジストリをカスケードします。
たとえば、この設定ファイルでは、
datastore.metric.host
とdatastore.metric.port
の両方が既に定義されています (上書きされても構いません)。さらにdatastore.metric.protocol
がデフォルトで定義されていれば、Viper はそれを見つけることができます。しかし、
datastore.metric
が (フラグ、環境変数、Set()
メソッドなどによって)即時値でオーバーライドされた場合、datastore.metric
のすべてのサブキーは未定義となり、より優先度の高い設定レベルによって "シャドウイング"されます。最終的に、区切り文字によるキーパスを指定してキーが見つかったら、その値を返します。次の例をご覧ください。
{ "datastore.metric.host": "0.0.0.0", "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } } GetString("datastore.metric.host") //"0.0.0.0" を返すサブツリーを展開する
Viperでサブツリーを展開できます。
変数
viper
が以下のようになっているとします。app: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80以下を実行すると、
subv := viper.Sub("app.cache1")
subv
の内容は以下のようになります。max-items: 100 item-size: 64
subv
の形式の設定情報を使用してキャッシュを作成する以下のような関数が既にあるとします。func NewCache(cfg *Viper) *Cache {...}前述の記法を使えば、以下のように2とおりのキャッシュを簡単に作成できます。
cfg1 := viper.Sub("app.cache1") cache1 := NewCache(cfg1) cfg2 := viper.Sub("app.cache2") cache2 := NewCache(cfg2)アンマーシャリング
特定の値(またはすべての値)をアンマーシャリングして、構造体やマップなどに保存できます。
以下の2つのメソッドを使用できます。
Unmarshal(rawVal interface{}) : error
UnmarshalKey(key string, rawVal interface{}) : error
例:
type config struct { Port int Name string PathMap string `mapstructure:"path_map"` } var C config err := viper.Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) }キー自体にドット(デフォルトのキー区切り文字)が含まれている設定をアンマーシャリングしたい場合。
区切り文字を変更する必要があります。v := viper.NewWithOptions(viper.KeyDelimiter("::")) v.SetDefault("chart::values", map[string]interface{}{ "ingress": map[string]interface{}{ "annotations": map[string]interface{}{ "traefik.frontend.rule.type": "PathPrefix", "traefik.ingress.kubernetes.io/ssl-redirect": "true", }, }, }) type config struct { Chart struct{ Values map[string]interface{} } } var C config v.Unmarshal(&C)Viperは、組み込み構造体へのアンマーシャリングもサポートしています。
/* Example config: module: enabled: true token: 89h3f98hbwf987h3f98wenf89ehf */ type config struct { Module struct { Enabled bool moduleConfig `mapstructure:",squash"` } } // moduleConfig could be in a module specific package type moduleConfig struct { Token string } var C config err := viper.Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) }Viperは値のアンマーシャリングにgithub.com/mitchellh/mapstructureを使用しており、デフォルトでは
mapstructure
タグを使用しています。文字列へのマーシャリング
viperに保持されているすべての設定をファイルに書き込むのではなく、文字列にまとめる必要があるかもしれません。
AllSettings()
が返す設定を使って、好きなフォーマットのマーシャラーを使うことができます。import ( yaml "gopkg.in/yaml.v2" // ... ) func yamlStringSettings() string { c := viper.AllSettings() bs, err := yaml.Marshal(c) if err != nil { log.Fatalf("unable to marshal config to YAML: %v", err) } return string(bs) }シングルトンか、インスタンス化か?
Viperは設定や初期化を行わなくても、すぐに使うことができます。多くのアプリケーションでは設定をひとつのリポジトリに集約しているので、Viperパッケージはそうしたスタイルを想定して、シングルトン的に使えます。
ここまでに使用したViperの使用例では、すべてシングルトン的スタイルを使用しています。
Viperインスタンスを複数使用する
そしてもちろん、アプリケーションでViperのインスタンスを複数使用することもできます。各インスタンスは設定や値を独自に持つことができます。それぞれ別の設定ファイルやキー/バリューストアなどから設定を読み込むこともできます。Viperパッケージでサポートする関数は、すべてviperのメソッドとしてミラーリングされます。
例:
x := viper.New() y := viper.New() x.SetDefault("ContentDir", "content") y.SetDefault("ContentDir", "foobar") //...Viperインスタンスを複数使用する場合は、各自でインスタンスを管理してください。
Q & A
Q: なんで「Viper」(マムシ)なんですか?
A: Cobraというライブラリとセットで使う設計なので、コブラとくればやはりマムシかなと。もうひとつ、G.I.ジョーのViperにもかけてあります。日本ならさしずめケロロ軍曹のヴァイパーですかね。ViperとCobraは互いに完全に独立しているのでそれぞれ単独でも使用できますが、組み合わせるとさらに強力になります。
Q: なんで「Cobra」なんですか?
A: コブラコマンダー(G.I.ジョー)が好きだからに決まってるっしょ。
- 投稿日:2020-07-07T15:56:49+09:00
golangci-lintに自作linterを追加する
はじめに
golangci-lintは、
golangci-lint run
とするだけで、様々な有名なlinterを実行することができます。これには以下のメリットがあります。
- 各linterの導入方法や設定方法を覚える必要なく、共通の設定で管理できる
- 自分でlinter探さなくても、良いlinterが日々追加されていく
- 誤検出がある場合に、linterによらず共通の設定方法でその結果を無視することができる
この記事では、golangci-lintに自作linterを追加する方法を公式ドキュメントと実際にsonatardがPRを出したファイルを参考にして紹介します。
golangci/golangci-lint - Add Noctx #1179他にも日本の方では timakinさん作のbodyclose、nakabonneさん作のnestif がgolangci-lintに採用されています。
公開Linterをgolangci-lintに追加する
0. Linterを実装する
golangci-lintは go/analysis packageを利用していないLinterをマージしないので、必ず利用するようにしましょう。
golangci-lint公式が紹介しているWriting Useful go/analysis Linterやtenntennさんが公開されている資料を参考に実装してみてください。
1. fork
https://github.com/golangci/golangci-lint を fork します。
2. テストの追加
Linterのためのテストを実装します。
test/testdata/{yourlintername}.go
に テストのファイルを追加します。サンプルそして
T=yourlintername.go make test_linters
を実行して、テストが失敗することを確認します。3. Linterの追加
pkg/golinters/{yourlintername}.go
という新しいファイルを追加します。 このディレクトリ内の他のLinterの実装を参考にしてください。 サンプル
pkg/golinters/{yourlintername}.go
で実装したLinterの新しい構造体をpkg/lint/lintersdb/manager.go
のGetAllSupportedLinterConfigs
関数に追加します。 確実な場合のみデフォルトで有効にしてください。 サンプル私が作成したnoctx
はデフォルトではオフになっています。
T=yourlintername.go make test_linters
でテストが通ることを確認します。4. Linterのオプションの設定
Linterに設定する必要があるオプションを調べます。例えば、
govet
にはcheck-shadowing
1つのオプションしかありません。golangci-lintのユーザーの迷惑にならないようにデフォルト値を選択します。 設定ファイルの例 .golangci.example.ymlgolangci-lintプロジェクト向けのgolangci-lintの設定にデフォルト値以外を設定する必要があるのならば .golangci.ymlに追加します。
config struct - pflag が適切な設定ファイルを解析するためのmapstructureタグを忘れないでください。
5. PRを送る
プルリクエストを送ると、CIが走るのですべて通過することを確認します。
またbotによりCLAの同意が求められるので同意する必要があります。最後にレビューとそのコメントの修正が完了するとマージされて終了になります。
まとめ
既にテストさえ書いていれば数ファイルを修正するだけで、PRを送ることができます。
自作Linterを作成しても中々使ってもらうことは難しいので「多くの開発者に自作のLinterを使ってもらうことでGoコミュニティをより良くしたい!」と考える方は、是非golangci-lintにPRを送ってみてください。参考
- 投稿日:2020-07-07T15:39:24+09:00
context.ContextなしでHTTP Requestを送信しているコードを発見する `noctx`
はじめに
https://github.com/sonatard/noctx/
noctxは
context.Context
なしでHTTPリクエストを送信しているコードを発見します。もしあなたがライブラリ開発者であるなら
context.Context
をHTTPリクエストに渡していないと、ライブラリを利用するユーザはHTTPリクエストのキャンセルやトレース情報の取得ができません。そのためライブラリの中でHTTPリクエストを送信している場合には
noctx
を利用することで、そのような問題を防ぐことができます。特に会社からAPIのGo SDKを公開している場合には、noctxを利用することで多くのユーザが安心して利用することができるようになります。
インストール方法
$ go get -u github.com/sonatard/noctx/cmd/noctx使い方
$ go vet -vettool=`which noctx` main.go ./main.go:6:11: net/http.Get must not be calledgolangci-lintからの実行
noctx
を configで有効にする。.golangci.yml# デフォルトのlinterに加えてnoctxを追加する linters: enable: - noctx # もしくはすべてのlinterを有効にして不要なlinterを無効化する linters: enable-all: true disable: - xxx # 不要なlinter# configに基づいた実行 $ golangci-lint run # noctxだけを実行 golangci-lint run --disable-all -E noctx検出のルール
- 以下の関数を実行している場合はエラー
- net/http.Get
- net/http.Head
- net/http.Post
- net/http.PostForm
- (*net/http.Client).Get
- (*net/http.Client).Head
- (*net/http.Client).Post
- (*net/http.Client).PostForm
- http.NewRequest 関数から返された http.Request を他の関数に渡しているとエラー
検出した場合の修正方法
- HTTPリクエストの送信では
(*http.Client).Do(*http.Request)
メソッドを利用する。- Go 1.13以降では、
http.NewRequest
関数の代わりにhttp.NewRequestWithContext
関数を利用する。- Go 1.12以前では、
http.NewRequest
のあとに(http.Request).WithContext(ctx)
を実行する。
(http.Request).WithContext(ctx)
はhttp.Request
をコピーして返すためパフォーマンスのデメリットがあります。もしあなたの作成したライブラリがGo1.13以降しかサポートしないのであればhttp.NewRequestWithContext
を利用してください。修正のサンプル
もしあなたがライブラリの作者で既にcontextを受け付けない関数を提供してしまっている場合は、別途contextを受け付ける関数を定義して、既存の関数はcontextを受け付ける関数のラッパーにすることをお勧めします。
// 修正前 // HTTPリクエストを送信しているが、contextを受け付けていない func Send(body io.Reader) error { req,err := http.NewRequest(http.MethodPost, "http://example.com", body) if err != nil { return nil } _, err = http.DefaultClient.Do(req) if err !=nil{ return err } return nil }// 修正後 func Send(body io.Reader) error { // SendWithContextを呼び出し、contextにはcontext.Background()を渡す return SendWithContext(context.Background(), body) } // contextを受け付ける func SendWithContext(ctx context.Context, body io.Reader) error { // NewRequestをNewRequestWithContextに変更してcontextを渡す req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://example.com", body) if err != nil { return nil } _, err = http.DefaultClient.Do(req) if err != nil { return err } return nil }検出例
package main import ( "context" "net/http" ) func main() { const url = "http://example.com" http.Get(url) // want `net/http\.Get must not be called` http.Head(url) // want `net/http\.Head must not be called` http.Post(url, "", nil) // want `net/http\.Post must not be called` http.PostForm(url, nil) // want `net/http\.PostForm must not be called` cli := &http.Client{} cli.Get(url) // want `\(\*net/http\.Client\)\.Get must not be called` cli.Head(url) // want `\(\*net/http\.Client\)\.Head must not be called` cli.Post(url, "", nil) // want `\(\*net/http\.Client\)\.Post must not be called` cli.PostForm(url, nil) // want `\(\*net/http\.Client\)\.PostForm must not be called` req, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` cli.Do(req) ctx := context.Background() req2, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK cli.Do(req2) req3, _ := http.NewRequest(http.MethodPost, url, nil) // OK req3 = req3.WithContext(ctx) cli.Do(req3) f2 := func(req *http.Request, ctx context.Context) *http.Request { return req } req4, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` req4 = f2(req4, ctx) cli.Do(req4) req5, _ := func() (*http.Request, error) { return http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` }() cli.Do(req5) }参照
- 投稿日:2020-07-07T08:26:57+09:00
go修行17日目 Webサーバ
参考サイト
https://echo.labstack.com/guide
https://rightcode.co.jp/blog/information-technology/golang-introduction-rest-apiパッケージインストール
go get -u github.com/labstack/echo/...コード
- localhost:1323で起動する
package main import ( "net/http" "github.com/labstack/echo" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) e.Logger.Fatal(e.Start(":1323")) }PS C:\Users\yuta\go\src\server> go run .\server.go ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v4.1.16 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:1323ユーザー名を返す
package main import ( "net/http" "github.com/labstack/echo" ) func main() { e := echo.New() e.GET("/users/:name", getUserName) e.Logger.Fatal(e.Start(":1323")) } func getUserName(c echo.Context) error { name := c.Param("name") return c.String(http.StatusOK, name) }