kubernetes code-generatorの使い方

# code-generatorとは

code-generatorは、Kubernetes のAPI typeを実装するために、必要なコードを生成するツール。

このツールで生成できるものの基本は以下の4種類:

## 1. deepcopy: 
KubernetesのObjectはすべて[runtime.Object](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime#Object) を実装する必要がありそれに必要な`DeepCopyObject`生成する

```go
type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}
```

> Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are expected to be serialized to the wire, the interface an Object must provide to the Scheme allows serializers to set the kind, version, and group the object is represented as. An Object may choose to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized.


## 2. client

Kubernetes APIにアクセスするクライアント。
Kubernetesのbuilt-inリソースの場合は、[clientset](https://github.com/kubernetes/client-go/blob/master/kubernetes/clientset.go)を使ってアクセスする。API groupごとに一つのversionを持つ。

```go
// Clientset contains the clients for groups. Each group has exactly one
// version included in a Clientset.
type Clientset struct {
	*discovery.DiscoveryClient
	admissionregistrationV1      *admissionregistrationv1.AdmissionregistrationV1Client
	admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
	internalV1alpha1             *internalv1alpha1.InternalV1alpha1Client
	appsV1                       *appsv1.AppsV1Client
	appsV1beta1                  *appsv1beta1.AppsV1beta1Client
	appsV1beta2                  *appsv1beta2.AppsV1beta2Client
	...
}
```

自分でカスタムリソースを作成する場合には、clientsetは自分で作成する必要があるので、その場合にcode-generatorを使って生成する。

```go
// Clientset contains the clients for groups. Each group has exactly one
// version included in a Clientset.
type Clientset struct {
	*discovery.DiscoveryClient
	exampleV1alpha1 *examplev1alpha1.ExampleV1alpha1Client
}
```

## 3. lister

in-memoryのcache.Indexerからオブジェクトをリストする

```go
// FooLister helps list Foos.
// All objects returned here must be treated as read-only.
type FooLister interface {
	// List lists all Foos in the indexer.
	// Objects returned here must be treated as read-only.
	List(selector labels.Selector) (ret []*v1alpha1.Foo, err error)
	// Foos returns an object that can list and get Foos.
	Foos(namespace string) FooNamespaceLister
	FooListerExpansion
}

// fooLister implements the FooLister interface.
type fooLister struct {
	indexer cache.Indexer
}
```

## 4. informer

対象となるKubernetes Objectの変更を検知して、Create, Update, Deleteに対応する処理などをする。Custom Resource作成時にcode-generatorで生成し、Custom Controller内で使われる。

```go
// FooInformer provides access to a shared informer and lister for
// Foos.
type FooInformer interface {
	Informer() cache.SharedIndexInformer
	Lister() v1alpha1.FooLister
}

type fooInformer struct {
	factory          internalinterfaces.SharedInformerFactory
	tweakListOptions internalinterfaces.TweakListOptionsFunc
	namespace        string
}
```

# 生成 (Step by Step)

## 1. `Foo`というAPI typeを定義する

```
mkdir code-generator-training && cd code-generator-training
git init
go mod init code-generator-training
```

```
go get k8s.io/apimachinery@v0.24.2
go get k8s.io/client-go@v0.24.2
go get k8s.io/code-generator@v0.24.2
```

```go:pkg/api/example.com/v1alpha1/types.go
package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Foo is a specification for a Foo resource
type Foo struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   FooSpec   `json:"spec"`
	Status FooStatus `json:"status"`
}

// FooSpec is the spec for a Foo resource
type FooSpec struct {
	DeploymentName string `json:"deploymentName"`
	Replicas       *int32 `json:"replicas"`
}

// FooStatus is the status for a Foo resource
type FooStatus struct {
	AvailableReplicas int32 `json:"availableReplicas"`
}

// FooList is a list of Foo resources
type FooList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Foo `json:"items"`
}
```


## 2. code-generatorでdeepcopyを生成する

### 2.1. DeepCopyとDeepCopyInfoを生成するためのコメントをつける

Packageに対して生成する場合: 以下のコメントを `doc.go` につける

```go
// +k8s:deepcopy-gen=package
```

typeごとに生成する場合: 以下のコメントをtypeにつける

```go
// +k8s:deepcopy-gen=true
```

今回は、packageに対して生成

```go:pkg/api/example.com/v1alpha1/doc.go
// +k8s:deepcopy-gen=package
// +groupName=example.com

package v1alpha1
```

### 2.2. DeepCopyInterfaceNameを生成するためのコメントをつける (Optional)

`+k8s:deepcopy-gen:interfaces=<interface>`をtypeにつけることで、DeepCopyInterfaceNameを生成できる

例.

```go
// +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object,k8s.io/kubernetes/runtime.List
```

この場合 `DeepCopyObject`と`DeepCopyList`が、返り値に指定したinterfaceを返すように生成される。


もう一つは、 `// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object`をFooとFooListの上に追加する (typeのコメントから1行開けておく)

```go:pkg/api/example.com/v1alpha1/types.go
...
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Foo is a specification for a Foo resource
type Foo struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   FooSpec   `json:"spec"`
	Status FooStatus `json:"status"`
}
...

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// FooList is a list of Foo resources
type FooList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Foo `json:"items"`
}
```

### 2.3. 生成する

https://github.com/kubernetes/code-generator の`generate-groups.sh`を使って生成する。

```
git clone https://github.com/kubernetes/code-generator
```
generate-groups.shを使って生成する:
```
generate-groups.sh 
Usage: generate-groups.sh <generators> <output-package> <apis-package> <groups-versions> ...

  <generators>        the generators comma separated to run (deepcopy,defaulter,client,lister,informer) or "all".
  <output-package>    the output package name (e.g. github.com/example/project/pkg/generated).
  <apis-package>      the external types dir (e.g. github.com/example/api or github.com/example/project/pkg/apis).
  <groups-versions>   the groups and their versions in the format "groupA:v1,v2 groupB:v1 groupC:v2", relative
                      to <api-package>.
  ...                 arbitrary flags passed to all generator binaries.


Examples:
  generate-groups.sh all             github.com/example/project/pkg/client github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
  generate-groups.sh deepcopy,client github.com/example/project/pkg/client github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
```

生成: 

```
code-generator/generate-groups.sh deepcopy code-generator-training/pkg/client code-generator-training/pkg/api example.com:v1alpha1 --go-header-file code-generator/hack/boilerplate.go.txt --output-base $(pwd)/..
```

`pkg/api/example.com/v1alpha1/zz_generated.deepcopy.go`が作成される

## 3. clientを生成

### 3.1. `// +genclient` コメントをつける

```go:pkg/api/example.com/v1alpha1/types.go
package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
...
```

### 3.2. 生成する

```
code-generator/generate-groups.sh client code-generator-training/pkg/client code-generator-training/pkg/api example.com:v1alpha1 --go-header-file code-generator/hack/boilerplate.go.txt --output-base $(pwd)/..
```

以下のファイルが生成される

```
pkg/client/clientset/versioned/
├── clientset.go
├── doc.go
├── fake
│   ├── clientset_generated.go
│   ├── doc.go
│   └── register.go
├── scheme
│   ├── doc.go
│   └── register.go
└── typed
	└── example.com
		└── v1alpha1
			├── doc.go
			├── example.com_client.go
			├── fake
			│   ├── doc.go
			│   ├── fake_example.com_client.go
			│   └── fake_foo.go
			├── foo.go
			└── generated_expansion.go

6 directories, 14 files
```

## 4. listerの生成

```
code-generator/generate-groups.sh lister code-generator-training/pkg/client code-generator-training/pkg/api example.com:v1alpha1 --go-header-file code-generator/hack/boilerplate.go.txt --output-base $(pwd)/..
```

以下のファイルが生成される:

```
tree pkg/client/listers
pkg/client/listers
└── example.com
	└── v1alpha1
		├── expansion_generated.go
		└── foo.go

2 directories, 2 files
```

## 5. informerの生成

※informerはlisterに依存しているので、informerを生成するときはlisterを生成しておく必要がある

```
code-generator/generate-groups.sh informer code-generator-training/pkg/client code-generator-training/pkg/api example.com:v1alpha1 --go-header-file code-generator/hack/boilerplate.go.txt --output-base $(pwd)/..
```

以下のファイルが生成される

```
tree pkg/client/informers
pkg/client/informers
└── externalversions
	├── example.com
	│   ├── interface.go
	│   └── v1alpha1
	│       ├── foo.go
	│       └── interface.go
	├── factory.go
	├── generic.go
	└── internalinterfaces
		└── factory_interfaces.go

4 directories, 6 files
```

# 生成 (まとめて)

## 1. types.goを作成

```go:pkg/api/example.com/v1alpha1/types.go
package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Foo is a specification for a Foo resource
type Foo struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   FooSpec   `json:"spec"`
	Status FooStatus `json:"status"`
}

// FooSpec is the spec for a Foo resource
type FooSpec struct {
	DeploymentName string `json:"deploymentName"`
	Replicas       *int32 `json:"replicas"`
}

// FooStatus is the status for a Foo resource
type FooStatus struct {
	AvailableReplicas int32 `json:"availableReplicas"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// FooList is a list of Foo resources
type FooList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Foo `json:"items"`
}
```

## 2. doc.goを作成

```go:pkg/api/example.com/v1alpha1/doc.go
// +k8s:deepcopy-gen=package
// +groupName=example.com

package v1alpha1
```

## 3. deepcopy,client,lister,informerすべてを生成

```
code-generator/generate-groups.sh all code-generator-training/pkg/client code-generator-training/pkg/api example.com:v1alpha1 --go-header-file code-generator/hack/boilerplate.go.txt --output-base $(pwd)/..
```

# 生成されたコードの使い方

生成されたdeepcopy,client,lister,informerの使い方は https://github.com/nakamasato/code-generator-training で試したので参考にしていただければと。
大体Custom Controller作成するときに出てくるので、https://github.com/kubernetes/sample-controller も参考になる。

# 参考資料

1. https://pkg.go.dev/k8s.io/gengo/examples/deepcopy-gen
1. https://github.com/kubernetes/sample-controller
1. https://github.com/nakamasato/code-generator-training