广告位联系
返回顶部
分享到

go语言代码生成器code generator使用介绍

Golang 来源:互联网 作者:秩名 发布时间:2022-05-13 23:50:59 人浏览
摘要

代码生成器介绍 client-go为每种k8s内置资源提供了对应的clientset和informer。那么我们要监听和操作自定义资源对象,应该如何做呢? 方式一:使用client-go提供的dynamicClient来操作自定义资

代码生成器介绍

client-go为每种k8s内置资源提供了对应的clientset和informer。那么我们要监听和操作自定义资源对象,应该如何做呢?

方式一:使用client-go提供的dynamicClient来操作自定义资源对象,当然由于dynamicClient是基于RESTClient实现的,所以我们可以使用RESTClient来达到同样的目的。

方式二: 使用conde-generator来帮我们生成我们需要的代码,这样我们就可以像使用client-go为k8s内置资源对象提供的方式监听和操作自定义资源了。

code-generator

code-generator 就是 Kubernetes 提供的一个用于代码生成的项目,它提供了以下工具为 Kubernetes 中的资源生成代码:

  • deepcopy-gen: 生成深度拷贝方法,为每个 T 类型生成 func (t* T) DeepCopy() *T 方法,API 类型都需要实现深拷贝
  • client-gen: 为资源生成标准的 clientset
  • informer-gen: 生成 informer,提供事件机制来响应资源的事件
  • lister-gen: 生成 Lister,为 get 和 list 请求提供只读缓存层(通过 indexer 获取)

Informer 和 Lister 是构建控制器的基础,使用这4个代码生成器可以创建全功能的、和 Kubernetes 上游控制器工作机制相同的 production-ready 的控制器。

code-generator 还包含一些其它的代码生成器,例如 Conversion-gen 负责产生内外部类型的转换函数、Defaulter-gen 负责处理字段默认值。

大部分的生成器支持--input-dirs参数来读取一系列输入包,处理其中的每个类型,然后生成代码:

? 1、部分代码生成到输入包所在目录,例如 deepcopy-gen 生成器,也可以使用参数--output-file-base "zz_generated.deepcopy" 来定义输出文件名

? 2、其它代码生成到 --output-package 指定的目录,例如 client-gen、informer-gen、lister-gen 等生成器

示例

接来下我们使用code-generator进行实战演示:

首先我们将项目拉到本地:

1

2

$ git clone https://github.com/kubernetes/code-generator.git

$ git checkout 0.23.3

然后我们进入到cmd目录下,就会看到我们上面介绍的工具:

接着我们对client-gen,deepcopy-gen,infromer-gen,lister-gen进行安装,会安装到GOPATH的bin目录下:

1

2

3

4

5

6

7

8

9

# 进行安装

$ go install ./cmd/{client-gen,deepcopy-gen,informer-gen,lister-gen}

# 获取GOPATH路径

$ go env | grep GOPATH

GOPATH="/Users/Christian/go"

# 查看

ls /Users/Christian/go/bin

client-gen     deepcopy-gen   goimports      lister-gen

controller-gen defaulter-gen  informer-gen   type-scaffold

发现我们已经成功的安装了,这时候我们就可以直接使用这些工具了,比如我们可以使用--help命令来查看如何使用client-gen:

当然通常情况下我们不会去单独的使用某一个工具。

接下来我们来创建我们的项目,此处我们可以仿照sample controller项目进行编写:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

$ mkdir operator-test && cd operator-test

$ go mod init operator-test

$ mkdir -p pkg/apis/example.com/v1

?  operator-test tree

.

├── go.mod

├── go.sum

└── pkg

    └── apis

        └── example.com

            └── v1

                ├── doc.go

                ├── register.go

                └── types.go

4 directories, 5 files

接下来我们对v1下面的三个go文件进行填充(可以直接复制sample-controller,对其进行做简单修改):

doc.go主要是用来声明要使用deepconpy-gen以及groupName。

1

2

3

4

// pkg/crd.example.com/v1/doc.go

// +k8s:deepcopy-gen=package

// +groupName=example.com

package v1

types.go主要是定义crd资源对应的go中的结构。

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

29

// pkg/crd.example.com/v1/types.go

package v1

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

// +genclient

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

// Bar is a specification for a Bar resource

type Bar struct {

    metav1.TypeMeta   `json:",inline"`

    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec BarSpec `json:"spec"`

    // Status BarStatus `json:"status"`

}

// BarSpec is the spec for a Bar resource

type BarSpec struct {

    DeploymentName string `json:"deploymentName"`

    Image          string `json:"image"`

    Replicas       *int32 `json:"replicas"`

}

// BarStatus is the status for a Bar resource

type BarStatus struct {

    AvailableReplicas int32 `json:"availableReplicas"`

}

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

// BarList is a list of Bar resources

type BarList struct {

    metav1.TypeMeta `json:",inline" :"metav1.TypeMeta"`

    metav1.ListMeta `json:"metadata" :"metav1.ListMeta"`

    Items []Bar `json:"items" :"items"`

}

register.go顾名思义,就是注册资源。

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

29

30

31

package v1

import (

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

    "k8s.io/apimachinery/pkg/runtime"

    "k8s.io/apimachinery/pkg/runtime/schema"

)

// SchemeGroupVersion is group version used to register these objects

var SchemeGroupVersion = schema.GroupVersion{Group: "example.com", Version: "v1"}

// Kind takes an unqualified kind and returns back a Group qualified GroupKind

func Kind(kind string) schema.GroupKind {

    return SchemeGroupVersion.WithKind(kind).GroupKind()

}

// Resource takes an unqualified resource and returns a Group qualified GroupResource

func Resource(resource string) schema.GroupResource {

    return SchemeGroupVersion.WithResource(resource).GroupResource()

}

var (

    // SchemeBuilder initializes a scheme builder

    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)

    // AddToScheme is a global function that registers this API group & version to a scheme

    AddToScheme = SchemeBuilder.AddToScheme

)

// Adds the list of known types to Scheme.

func addKnownTypes(scheme *runtime.Scheme) error {

    scheme.AddKnownTypes(SchemeGroupVersion,

        &Bar{},

        &BarList{},

    )

    metav1.AddToGroupVersion(scheme, SchemeGroupVersion)

    return nil

}

这时候会发现&Bar{},&BarLis{}会报错,这是因为我们还没有为其实现deepcopy方法。

由于在自动生成代码的时候,需要指定header的信息,所以我们为了方便,可以将code-generator项目下的hack包直接拷贝到我们当前项目根目录下。

接下来我们使用code-generator来为我们自动生成代码:

1

2

3

4

5

6

7

8

9

10

11

12

# 运行 code-generator/generate-group.sh

./../../github/code-generator/generate-groups.sh all \

# 指定 group 和 version,生成deeplycopy以及client

operator-test/pkg/client operator-test/pkg/apis crd.example.com:v1 \

# 指定头文件

--go-header-file=./hack/boilerplate.go.txt \

# 指定输出位置,默认为GOPATH

--output-base ../

Generating deepcopy funcs

Generating clientset for crd.example.com:v1 at operator-test/pkg/client/clientset

Generating listers for crd.example.com:v1 at operator-test/pkg/client/listers

Generating informers for crd.example.com:v1 at operator-test/pkg/client/informers

这时候我们再来查看项目结构:

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

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

?  operator-test tree

.

├── go.mod

├── go.sum

├── hack

│   └── boilerplate.go.txt

└── pkg

    ├── apis

    │   └── crd.example.com

    │       └── v1

    │           ├── doc.go

    │           ├── register.go

    │           ├── types.go

    │           └── zz_generated.deepcopy.go

    └── client

        ├── clientset

        │   └── versioned

        │       ├── clientset.go

        │       ├── doc.go

        │       ├── fake

        │       │   ├── clientset_generated.go

        │       │   ├── doc.go

        │       │   └── register.go

        │       ├── scheme

        │       │   ├── doc.go

        │       │   └── register.go

        │       └── typed

        │           └── crd.example.com

        │               └── v1

        │                   ├── bar.go

        │                   ├── crd.example.com_client.go

        │                   ├── doc.go

        │                   ├── fake

        │                   │   ├── doc.go

        │                   │   ├── fake_bar.go

        │                   │   └── fake_crd.example.com_client.go

        │                   └── generated_expansion.go

        ├── informers

        │   └── externalversions

        │       ├── crd.example.com

        │       │   ├── interface.go

        │       │   └── v1

        │       │       ├── bar.go

        │       │       └── interface.go

        │       ├── factory.go

        │       ├── generic.go

        │       └── internalinterfaces

        │           └── factory_interfaces.go

        └── listers

            └── crd.example.com

                └── v1

                    ├── bar.go

                    └── expansion_generated.go

22 directories, 29 files

这时候我们就可以像操作内置资源一样,操作我们的自定义资源了。

我们先准备crd以及对应的cr,这边也是可以直接从sample-controller项目进行拷贝,做简单的修改即可。

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

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

# manifests/example.com_bars.yaml

---

apiVersion: apiextensions.k8s.io/v1

kind: CustomResourceDefinition

metadata:

  annotations:

    controller-gen.kubebuilder.io/version: (devel)

  creationTimestamp: null

  name: bars.crd.example.com

spec:

  group: crd.example.com

  names:

    kind: Bar

    listKind: BarList

    plural: bars

    singular: bar

  scope: Namespaced

  versions:

  - name: v1

    schema:

      openAPIV3Schema:

        description: Bar is a specification for a Bar resource

        properties:

          apiVersion:

            description: 'APIVersion defines the versioned schema of this representation

              of an object. Servers should convert recognized schemas to the latest

              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'

            type: string

          kind:

            description: 'Kind is a string value representing the REST resource this

              object represents. Servers may infer this from the endpoint the generated

              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'

            type: string

          metadata:

            type: object

          spec:

            description: BarSpec is the spec for a Bar resource

            properties:

              deploymentName:

                type: string

              image:

                type: string

              replicas:

                format: int32

                type: integer

            required:

            - deploymentName

            - image

            - replicas

            type: object

        required:

        - spec

        type: object

    served: true

    storage: true

# manifests/cr.yaml

---

apiVersion: crd.example.com/v1

kind: Bar

metadata:

  name: bar-demo

  namespace: default

spec:

  image: "nginx:1.17.1"

  deploymentName: example-bar

  replicas: 2

接下来我们来编写main函数,这时候我们就可以使用client-go像操作我们内置资源一样,操作crd资源了。

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

29

30

31

32

33

34

35

36

37

38

package main

import (

    "context"

    "fmt"

    v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

    "k8s.io/client-go/tools/cache"

    "k8s.io/client-go/tools/clientcmd"

    "log"

    clientSet "operator-test/pkg/client/clientset/versioned"

    "operator-test/pkg/client/informers/externalversions"

)

func main() {

    config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)

    if err != nil {

        log.Fatalln(err)

    }

    clientset, err := clientSet.NewForConfig(config)

    if err != nil {

        log.Fatalln(err)

    }

    list, err := clientset.CrdV1().Bars("default").List(context.TODO(), v1.ListOptions{})

    if err != nil {

        log.Fatalln(err)

    }

    for _, bar := range list.Items {

        fmt.Println(bar.Name)

    }

    factory := externalversions.NewSharedInformerFactory(clientset, 0)

    factory.Crd().V1().Bars().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{

        AddFunc:    nil,

        UpdateFunc: nil,

        DeleteFunc: nil,

    })

    // todo

}

// ====

// 程序输出结果:

bar-demo

代码生成tag

在我们上面的示例中,我们在源码中添加了很多tag,我们使用这些tag来标记一些供生成器使用的属性。这些tag主要分为两类:

  • 在doc.go的package语句智商提供的全局tag
  • 在需要被处理的类型上提供局部tag

tag的使用方法如下所示:

1

2

3

// +tag-name

// 或者

// +tag-name=value

我们可以看到 tag 是通过注释的形式存在的,另外需要注意的是 tag 的位置非常重要,很多 tag 必须直接位于 type 或 package 语句的上一行,另外一些则必须和 go 语句隔开至少一行空白。

全局tag

必须在目标包的doc.go文件中声明,一般路径为pkg/apis/<apigroup>/<version>/doc.go,如下所示:

1

2

3

4

5

6

7

// 为包中任何类型生成深拷贝方法,可以在局部 tag 覆盖此默认行为

// +k8s:deepcopy-gen=package

  

// groupName 指定 API 组的全限定名

// 此 API 组的 v1 版本,放在同一个包中

// +groupName=crd.example.com

package v1

注意:空行不能省略

局部tag

局部tag要么直接声明在类型之前,要么位于类型之前的第二个注释块中。下面的 types.go 中声明了 CR 对应的类型:

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

// 为当前类型生成客户端,如果不加此注解则无法生成 lister、informer 等包

// +genclient

// 提示此类型不基于 /status 子资源来实现 spec-status 分离,产生的客户端不具有 UpdateStatus 方法

// 否则,只要类型具有 Status 字段,就会生成 UpdateStatus 方法

// +genclient:noStatus

// 为每个顶级 API 类型添加,自动生成 DeepCopy 相关代码

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

// K8S 资源,数据库

type Database struct {

    metav1.TypeMeta   `json:",inline"`

    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec DatabaseSpec `json:"spec"`

}

// 不为此类型生成深拷贝方法

// +k8s:deepcopy-gen=false

// 数据库的规范

type DatabaseSpec struct {

    User     string `json:"user"`

    Password string `json:"password"`

    Encoding string `json:"encoding,omitempty"`

}

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

// 数据库列表,因为 list 获取的是列表,所以需要定义该结构

type DatabaseList struct {

    metav1.TypeMeta `json:",inline"`

    metav1.ListMeta `json:"metadata"`

    Items []Database `json:"items"`

}

在上面 CR 的定义上面就通过 tag 来添加了自动生成相关代码的一些注释。此外对于集群级别的资源,我们还需要提供如下所示的注释:

1

2

3

// +genclient:nonNamespaced

// 下面的 Tag 不能少

// +genclient

另外我们还可以控制客户端提供哪些 HTTP 方法:

1

2

3

4

5

6

7

// +genclient:noVerbs

// +genclient:onlyVerbs=create,delete

// +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch

// 仅仅返回 Status 而非整个资源

// +genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/meta/v1.Status

// 下面的 Tag 不能少

// +genclient

使用 tag 定义完需要生成的代码规则后,执行上面提供的代码生成脚本即可自动生成对应的代码了。

补充

除了上面介绍的代码生成方式,我们还可以直接使用sample-controller项目提供的hack/update-condegen.sh脚本。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#!/usr/bin/env bash

set -o errexit

set -o nounset

set -o pipefail

SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..

# 代码生成器包的位置

CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}

# generate-groups.sh <generators> <output-package> <apis-package> <groups-versions>

#                    使用哪些生成器,可选值 deepcopy,defaulter,client,lister,informer,逗号分隔,all表示全部使用

#                    输出包的导入路径 

#                    CR 定义所在路径

#                    API 组和版本

bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \

  k8s.io/sample-controller/pkg/generated k8s.io/sample-controller/pkg/apis \

  samplecontroller:v1alpha1 \

  --output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \

  --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt

# 自动生成的源码头部附加的内容:

#   --go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt

执行上面的脚本后,所有 API 代码会生成在 pkg/apis 目录下,clientsets、informers、listers 则生成在 pkg/generated 目录下。不过从脚本可以看出需要将 code-generator 的包放置到 vendor 目录下面,现在我们都是使用 go modules 来管理依赖保,我们可以通过执行 go mod vendor 命令将依赖包放置到 vendor 目录下面来。

我们还可以进一步提供 hack/verify-codegen.sh 脚本,用于判断生成的代码是否 up-to-date:

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

29

#!/usr/bin/env bash

set -o errexit

set -o nounset

set -o pipefail

# 先调用 update-codegen.sh 生成一份新代码

# 然后对比新老代码是否一样

SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..

DIFFROOT="${SCRIPT_ROOT}/pkg"

TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg"

_tmp="${SCRIPT_ROOT}/_tmp"

cleanup() {

  rm -rf "${_tmp}"

}

trap "cleanup" EXIT SIGINT

cleanup

mkdir -p "${TMP_DIFFROOT}"

cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}"

"${SCRIPT_ROOT}/hack/update-codegen.sh"

echo "diffing ${DIFFROOT} against freshly generated codegen"

ret=0

diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$?

cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}"

if [[ $ret -eq 0 ]]

then

  echo "${DIFFROOT} up to date."

else

  echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh"

  exit 1

fi


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7096484178128011277
相关文章
  • 基于GORM实现CreateOrUpdate的方法
    CreateOrUpdate 是业务开发中很常见的场景,我们支持用户对某个业务实体进行创建/配置。希望实现的 repository 接口要达到以下两个要求: 如果
  • Golang中的内存逃逸的介绍
    什么是内存逃逸分析 内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上 简单说就是编译器在编译
  • Golang自旋锁的介绍
    自旋锁 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。 它是为实现保护共享资源而提出的
  • Go语言读写锁RWMutex的源码

    Go语言读写锁RWMutex的源码
    在前面两篇文章中初见 Go Mutex、Go Mutex 源码详解,我们学习了Go语言中的Mutex,它是一把互斥锁,每次只允许一个goroutine进入临界区,可以保
  • Go项目实现优雅关机与平滑重启功能
    什么是优雅关机? 优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对
  • Go语言操作Excel利器之excelize类库的介绍
    在开发中一些需求需要通过程序操作excel文档,例如导出excel、导入excel、向excel文档中插入图片、表格和图表等信息,使用Excelize就可以方便
  • 利用Go语言快速实现一个极简任务调度系统

    利用Go语言快速实现一个极简任务调度系统
    任务调度(Task Scheduling)是很多软件系统中的重要组成部分,字面上的意思是按照一定要求分配运行一些通常时间较长的脚本或程序。在爬
  • GoLang中的iface 和 eface 的区别介绍

    GoLang中的iface 和 eface 的区别介绍
    GoLang之iface 和 eface 的区别是什么? iface和eface都是 Go 中描述接口的底层结构体,区别在于iface描述的接口包含方法,而eface则是不包含任何方
  • Golang接口使用的教程
    go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、
  • go colly 爬虫实现示例介绍
    贡献某CC,go源码爬虫一个,基于colly,效果是根据输入的浏览器cookie及excel必要行列号,从excel中读取公司名称,查询公司法人及电话号码。
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计