Go勉強(3)　kubernetes client-goのexamplesを読んでみる

# シリーズ
[Go勉強(1)　mac＋VSCode＋Go環境を設定](https://qiita.com/oruharo/items/545378eae5c707f717ed)
[Go勉強(2)　kubernetes client-goのexamplesをbuildする](https://qiita.com/oruharo/items/261d065340845149b4ce)
[Go勉強(3)　kubernetes client-goのexamplesを読んでみる](https://qiita.com/oruharo/items/8f98e75264b9d6c7df2a)
[Go勉強(4)　kubernetes client-goでPodのwatcher（TUI）を書いてみる]
(https://qiita.com/oruharo/items/668d708cead0ad261346)
[Go勉強(5)　kubernetes client-goでPodのwatcher（TUI）を書いてみる2]
(https://qiita.com/oruharo/items/b7a131bf3eda36dd08a3)

# はじめに

Golang自体の勉強をかねて、kubernetesへのアクセスライブラリ`client-go`を使ったサンプルソースを読んでみます。

■参考
・[初めてのGo](http://gihyo.jp/dev/feature/01/go_4beginners)
・[他言語プログラマがgolangの基本を押さえる為のまとめ](https://qiita.com/tfrcm/items/e2a3d7ce7ab8868e37f7)
・[他言語から来た人がGoを使い始めてすぐハマったこととその答え](https://qiita.com/mumoshu/items/0d2f2a13c6e9fc8da2a4)
・[Goで学ぶポインタとアドレス](https://qiita.com/Sekky0905/items/447efa04a95e3fec217f)
・[「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件](https://qiita.com/Maki-Daisuke/items/80cbc26ca43cca3de4e4)


# サンプルコード

https://github.com/kubernetes/client-go/blob/master/examples/out-of-cluster-client-configuration/main.go
このコード読みます。 全行掲載します。

```client-go/examples/out-of-cluster-client-configuration/main.go
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Note: the example only works with the code within the same release/branch.
package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	//
	// Uncomment to load all auth plugins
	// _ "k8s.io/client-go/plugin/pkg/client/auth"
	//
	// Or uncomment to load specific auth plugins
	// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
	// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
	// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)

func main() {
	var kubeconfig *string
	if home := homeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	// use the current context in kubeconfig
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// create the clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	for {
		pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
		if err != nil {
			panic(err.Error())
		}
		fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

		// Examples for error handling:
		// - Use helper functions like e.g. errors.IsNotFound()
		// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
		namespace := "default"
		pod := "example-xxxxx"
		_, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
		if errors.IsNotFound(err) {
			fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
		} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
			fmt.Printf("Error getting pod %s in namespace %s: %v\n",
				pod, namespace, statusError.ErrStatus.Message)
		} else if err != nil {
			panic(err.Error())
		} else {
			fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)
		}

		time.Sleep(10 * time.Second)
	}
}

func homeDir() string {
	if h := os.Getenv("HOME"); h != "" {
		return h
	}
	return os.Getenv("USERPROFILE") // windows
}
```

# コードリーディング
コードを見ながら気になるところをコメントしていきます。

### import パッケージ

importされいるパケージです。　リファレンスはhttps://godoc.org/ から検索できるようです。

|パッケージ        |説明          |
|-----------------|------------------|
|[flag](https://godoc.org/pkg/flag)| コマンドラインフラグ解析を実装します。|
|[fmt](https://godoc.org/pkg/fmt)|Cのprintfおよびscanfに類似した機能を持つフォーマットされたI / Oを実装します。 「動詞」の形式はCから派生していますが、より単純です。
|[os](https://godoc.org/pkg/os)|オペレーティングシステムの機能に対するプラットフォームに依存しないインターフェイスを提供します。エラー処理はGoに似ていますが、設計はUnixに似ています。失敗した呼び出しは、エラー番号ではなくエラータイプの値を返します。
|[path/filepath](https://godoc.org/pkg/path/filepath)|ターゲットオペレーティングシステム定義のファイルパスと互換性のある方法でファイル名パスを操作するためのユーティリティルーチンを実装します。
|[time](https://godoc.org/pkg/time)|時間を測定および表示する機能を提供します。
|[k8s.io/apimachinery/pkg/api/errors](https://godoc.org/k8s.io/apimachinery/pkg/api/errors)|APIフィールド検証の詳細なエラータイプを提供します。
|[k8s.io/apimachinery/pkg/apis/meta/v1](https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1)|すべてのバージョンに共通のAPIタイプが含まれています。
|[k8s.io/client-go/kubernetes](https://godoc.org/k8s.io/client-go/kubernetes)|自動生成されたクライアントセットです。
|[k8s.io/client-go/tools/clientcmd](https://godoc.org/k8s.io/client-go/tools/clientcmd)|固定構成、.kubeconfigファイル、コマンドラインフラグ、またはマージされた組み合わせから作業クライアントを構築するためのワンストップショッピングを提供します。

### main関数
mainパッケージのmain関数がエントリーポイントとなって最初に実行されます。 javaのstatic main関数と同じですね。

```go
func main() {
```

### ポインタ
C言語ではかなり苦しめられるポインタですが、Goの場合はポインタ渡し（参照渡し）・値渡しくらいのことを理解しとけばそんなに難しくなさそうです。

```go
    var kubeconfig *string
        :
    kubeconfig = flag.String( ... )
        :
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
```
↑`flag`が返す文字列のポインタを受け取って（ポインタ渡し）、`clientcmd`に文字列のコピーを渡してます(値渡し)。
↓ こうすると文字列のコピーを受け取って（値渡し)、さらにコピーを渡す（値渡し)ことになりますね。
文字列にポインタ渡しを使うのは無駄なコピー処理やメモリーを節約するのが目的でしょうね。
ポインタ渡しされた文字列を変更できるのかな。C言語的な考えだと文字列伸ばすと後ろのメモリが壊れるからダメなような。(後で調査）

```go
    var kubeconfig string
        :
    kubeconfig = *flag.String( ... )
        :
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
```
### if文 初期化ステートメント


```go
    if home := homeDir(); home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    }
```
この`if home := homeDir(); `の部分。 見慣れない構文です。 「初期化ステートメント」と言うそうです。
Goではswitch文にも同様に記述できます。 
他言語でよくある`for(int i=0; i<10; i++) {`の`i=0`の部分と同じ位置付けのものです。 

```go
    home := homeDir()
    if home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    }
```
他言語だったら↑こんな感じで書きますね。 初期化ステートメントは1行減らすための構文？って最初思いましたが、初期化ステートメントによって変数のスコープをif文内に閉じ込めることができるんですね。

```go
        if errors.IsNotFound(err) {
            :
        } else if statusError, isStatus := err.(*errors.StatusError); isStatus {
            :
        } else {
            :
        }
```
この部分↑が初期化ステートメントじゃなかったら、↓こんな感じにネストすることになってイヤですね。

```go
        if errors.IsNotFound(err) {
            :
        } else {
            statusError, isStatus := err.(*errors.StatusError);
            if  isStatus {
                :
            } else {
                :
            }
        }
```


### Flag

Pythonなんかにもあるコマンドライン パラメータを受け取ったり、usageを表示します。

```go
    kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    flag.Parse()
```
helpを出すとこんな感じになります。kubernetesのconfigファイルを指定する引数を渡せます。

```console
$ ./app --help
Usage of ./app:
  -kubeconfig string
        (optional) absolute path to the kubeconfig file (default "/Users/haruo/.kube/config")
```

### 多値返却
pythonのように複数の戻り値を簡単に返せて簡単に受け取れます。
使わない戻り値は"_"で受けとります。

```go
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
        :
    clientset, err := kubernetes.NewForConfig(config)
        :
    _, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
```
ん？ これだと`err`が二重宣言でビルドエラーじゃないの？ と思いましたが、
試してみたら`:=`を使う時、左辺に新しく宣言する変数が一つでもあればエラーにならないみたいです。

### エラー処理

C → java,C# → Goの順に経験すると、Cの`if （rc != 0)`だらけのコードに逆戻りするような感じがしてしまうのですが、Goのそれは一周まわった完成形って感じでしょうか。 

```go
    if err != nil {
        panic(err.Error())
    }
```

試しに誤ったconfigファイルを渡して無理やりエラーを発生させると、以下のような結果になりました。

```console
$ ./app -kubeconfig /Users/xxxxx/.ssh/config
panic: error loading config file "/Users/xxxxx/.ssh/config": couldn't get version/kind; json parse error: json: cannot unmarshal string into Go value of type struct { APIVersion string "json:\"apiVersion,omitempty\""; Kind string "json:\"kind,omitempty\"" }

goroutine 1 [running]:
main.main()
        /Users/xxxxx/go/src/out-of-cluster-client-configuration/main.go:54 +0x83a
```

### 繰り返し
繰り返しを表すキーワードは`for`のみ。`ｗｈｉｌｅ`は無し。シンプルですね。

```go
    for {
    }
```
これは無限ループです。 多言語だったら`while(true)`や`for(;;)`って書いたりします。

### キャスト
キャストとキャストできるかの判定を一発でできちゃいます。

```go
        } else if statusError, isStatus := err.(*errors.StatusError); isStatus {
            :
        }
```
`err`が`errors.StatusError`にキャストできるかを`isStatus`に格納、
`err`を`errors.StatusError`にキャストした結果をstatusErrorに格納します。

```hoge.java
        } else if (err instanceof StatusError) {
            StatusError statusError = (StatusError) err;
            :
        }
```
javaだったら↑こんな感じで書きます。 StatusErrorを３回も書かないといけないです。
でもisStatusは書かなくて済んでる。んー。

### time

```go
        time.Sleep(10 * time.Second)
```
time.Secondは以下のようにnano秒単位の倍数で定義されてます。

```/usr/local/Cellar/go/1.13.1/libexec/src/time/time.go
const (
	Nanosecond  Duration = 1
	Microsecond          = 1000 * Nanosecond
	Millisecond          = 1000 * Microsecond
	Second               = 1000 * Millisecond
	Minute               = 60 * Second
	Hour                 = 60 * Minute
)
```

### kubernetes

Client-goに関する部分です。 まだよくわかってないのでリファレンスとかだけ貼っておきます。
もっとわかってきたら書き足します。

```go
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    clientset, err := kubernetes.NewForConfig(config)
```
kube/configファイルのパスを渡して`Clientset`を取得します。

| リファレンス       |ソース|
|----------------|----|
|[rest.Config](https://godoc.org/k8s.io/client-go/rest#Config)|https://github.com/kubernetes/client-go/blob/master/rest/config.go#L52|
|[clientcmd.BuildConfigFromFlags](https://godoc.org/k8s.io/client-go/tools/clientcmd#BuildConfigFromFlags) |https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/client_config.go#L541|
|[Clientset](https://godoc.org/k8s.io/client-go/kubernetes#Clientset)|https://github.com/kubernetes/client-go/blob/master/kubernetes/clientset.go#L111|
|[kubernetes.NewForConfig](https://godoc.org/k8s.io/client-go/kubernetes#NewForConfig)|https://github.com/kubernetes/client-go/blob/master/kubernetes/clientset.go#L354|



```go
    pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
    ｐｏd, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
```
Podの情報を取得します。　

| リファレンス       |ソース|
|----------------|----|
|[PodList](https://godoc.org/k8s.io/api/core/v1#PodList)|https://github.com/kubernetes/api/blob/master/core/v1/types.go#L3536|
|[Pod](https://godoc.org/k8s.io/api/core/v1#Pod)|https://github.com/kubernetes/api/blob/master/core/v1/types.go#L3512|
|[CoreV1Client.Pods](https://godoc.org/k8s.io/client-go/kubernetes/typed/core/v1#CoreV1Client.Pods)|https://github.com/kubernetes/client-go/blob/master/kubernetes/typed/core/v1/core_client.go#L88|
|[PodInterface](https://godoc.org/k8s.io/client-go/kubernetes/typed/core/v1#PodInterface)|https://github.com/kubernetes/client-go/blob/master/kubernetes/typed/core/v1/pod.go#L39|
|[metav1.ListOptions](https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#ListOptions)|https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go#L328|
|[metav1.GetOptions](https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#GetOptions)|https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go#L421|



# 終わりに
短いコードですけど、Goの特徴がいくつも出てきました。
Goの理解はまだまだですけど、次からコード書いていきます。