美文网首页
Rancher 二次开发之自定义创建 API

Rancher 二次开发之自定义创建 API

作者: 河码匠 | 来源:发表于2023-01-16 09:33 被阅读0次

本次最终目的是在 rancher 内创建一个新的 API。
最终效果如下图

v3 根目录 link 中 faas api faa api 列表页也就是 collection faa 详情页,以及一个 action

相关目录结构会在开发过程中介绍。

1. 定义 API types

pkg/apis/ 文件夹就是在定义 rancher api 的 types
例如:
pkg/apis/project.cattle.io/v3/types.go 中定义了 workload
pkg/apis/management.cattle.io/v3 中定义是 client 相关的 types;faa api 示例也是放在这里面的
pkg/apis/cluster.cattle.io/v3 中定义的是 cluster 相关的 typess

新建文件 pkg/apis/management.cattle.io/v3/faa_types.go
package v3

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

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

type Faa struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec1   FaaSpec   `json:"spec1"`
    Status1 FaaStatus `json:"status1"`
    // 这里的 json:"asf1" 定义 api 字段名;如图:"定义返回的数据结构" data 中的 asf1
    Asf     string    `json:"asf1" norman:"default=bbbb"`
}

type FaaSpec struct {
    // norman:"nullable" norman 规范
    Bar1 bool `json:"bar1" norman:"nullable"`
}

type FaaStatus struct {
    Msg1 string `json:"msg1"`
}

// action 入参结构体
type EchoActionInput struct {
    Echo bool `json:"echo"`
    Aaaa bool `json:"aaaa"`

代码说明

5~7 行的注释是有意义的。

pkg/apis/${GROUP}/${VERSION}/types.go 中使用,使用 // +genclient 标记对应类型生成的客户端,如果与该类型相关联的资源不是命名空间范围的(例如 PersistentVolume )
则还需要附加// + genclient:nonNamespaced标记

参数 说明
// +genclient 生成默认的客户端动作函数(create, update, delete, get, list, update, patch, watch 这些取决于生成客户端的类型中是否存在 .Status 字段以及 updateStatus
// +genclient:nonNamespaced 所有动作函数都是在没有名称空间的情况下生成
// +genclient:onlyVerbs=create,get 指定的动作函数被生成
// +genclient:skipVerbs=watch 生成 watch 以外所有的动作函数
// +genclient:noStatus 即使 .Status 字段存在也不生成 updateStatus 动作函数

conversion-gen 是用于自动生成在内部和外部类型之间转换的函数的工具。
一般的转换代码生成任务涉及三套程序包:

  1. 一套包含内部类型的程序包;
  2. 一套包含外部类型的程序包;
  3. 单个目标程序包(即,生成的转换函数所在的位置,以及开发人员授权的转换功能所在的位置);
参数 说明
// +k8s:conversion-gen=<import-path-of-internal-package> 标记转换内部软件包
// +k8s:conversion-gen-external-types=<import-path-of-external-package> 标记转换外部软件包
// +k8s:conversion-gen=false 标记不转换对应注释或结构
  • deepcopy-gen

deepcopy-gen 是用于自动生成 DeepCopy 函数的工具

参数 说明
// +k8s:deepcopy-gen=package 在文件中添加注释
// +k8s:deepcopy-gen=true 为单个类型添加自动生成
// +k8s:deepcopy-gen=false 为单个类型关闭自动生成
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 指在生成 DeepCopy 时,实现 Kubernetes 提供的 runtime.Object 接口
  • defaulter-gen
参数 说明
// +k8s:defaulter-gen=<field-name-to-flag> 为包含字段的所有类型创建defaulters
// +k8s:defaulter-gen=true|false 所有都生成
9~16 定义 API 结构体包括返回的数据等信息,在 apiRoot 下面的 links 中的名称,就是结构体 faa 的名字,如下图
api root 下 api
18~25 定义 data 中的两个返回数据的结构体,结果如下图。

关于第 20 行 norman 的规范参考这里

定义返回的数据结构
28~31 定义一个 Action 操作以及操作中的 input 内容,如下图
按钮 input 内容定义

2. 将 1 中定义的 types 引入

目录 /pkg/schemas/ 目录存放的是 api types 引入的 schema 文件
示例:
/pkg/schemas/management.cattle.io/v3/schema.go 中中定义 client 相关 types 的引用
/pkg/schemas/cluster.cattle.io/v3/schema.go 中定义 cluster 相关 types 引用
/pkg/schemas/project.cattle.io/v3/schema.go 中的有 workloadTypes 的引用

修改 /pkg/schemas/management.cattle.io/v3/schema.go

var (
    Version = types.APIVersion{
        Version: "v3",
        Group:   "management.cattle.io",
        Path:    "/v3",
    }

    AuthSchemas = factory.Schemas(&Version).
            Init(authnTypes).
            Init(tokens).
            Init(userTypes)

    Schemas = factory.Schemas(&Version).
        Init(nativeNodeTypes).
        // ........
        Init(notificationTypes).
        // 初始化 faaTypes 函数 
        Init(faaTypes)

    TokenSchemas = factory.Schemas(&Version).
            Init(tokens)
)

// 定义 API 中的字段信息,以及 API type 为 Collection 或 resource 中的 action 
func faaTypes(schemas *types.Schemas) *types.Schemas {
    return schemas.

        // TypeName 是为了让 schemas 知道一个结构体 v3.Faa{} 已经引入并且映射到 faa;
        // 在 schemas 中给 Faa{} 结构体定义名称。如图 2-1
        TypeName("faa", v3.Faa{}).
        TypeName("faaSpec", v3.FaaSpec{}).
        TypeName("faaStatus", v3.FaaStatus{}).

        // MustImport 把定义好的结构体导入导 schema 中
        // 这个 v3.EchoActionInput{} 就是点击 echo1 action 按钮后出入的参数,会将这个参数转换为结构体;  如图: "按钮 input 内容定义"
        MustImport(&Version, v3.EchoActionInput{}).

        // 导入和自定义 input 的同时额外定义一些内容 
        // 注:必须先  MustImport "EchoActionInput" 才能够在这里定义 input: "echoActionInput"
        MustImportAndCustomize(&Version, v3.Faa{}, func(schema *types.Schema) {
        
            // schema.CollectionActions 定义 collection 中要添加的 action 方法

            // schema.CollectionMethods 定义 Collection 中只允许 GET 操作; 如图 2-2
            schema.CollectionMethods = []string{http.MethodGet}

            // 这里没有定义 schema.ResourceMethods 所以增删改查权限都有;  如图 2-3
            // schema.ResourceActions 定义一个 echo1 动作绿色按钮;  如图 2-3
            schema.ResourceActions = map[string]types.Action{
                "echo1": {
                    // 将 types.Action 类型传递给 schema.ResourceActions 告诉 schema 中已经定义的 echo1 他的 input 表单有那些字段
                    // echoActionInput 在 faa_types.go 文件中定义的结构体
                    Input: "echoActionInput",
                },
            }
        })
}

代码说明。看注释

2-1 2-2 2-3

3. 执行 go generate

在项目根目录执行 go generate。先执行删除旧 zz 文件。然后创建新的 zz 文件,时间比较长
rancher 会根据 types 的变化重新创建 zz_generate_xxx 相关的文件
如:
pkg/client/generated/management/v3/zz_generate_faa.go
pkg/client/generated/management/v3/zz_generate_faa_spec.go
pkg/client/generated/management/v3/zz_generate_faa_status.go
pkg/client/generated/management/v3/zz_generate_echo_action_input.go
就是 faa_types.go 文件中定义的结构体,每个结构体都会生成一个 zz_generate_xxx 的文件

4. 在 norman 中实现自定义 Action 的功能

目录 /pkg/api/norman/ 定义 API 要实现的一些功能
例如:
/pkg/api/norman/customization/faa/faa.go 定义了 faa api 中 action 按钮的映射 Formatter 函数,和按钮要执行的操作 ActionHandler 函数;
例如:
/pkg/api/norman/store/workload/workload_store.go 中定义了 workload Resource api 中 action 按钮方法的实现

新建文件 /pkg/api/norman/customization/faa/faa.go
package faa

import (
    "encoding/json"
    "fmt"
    "github.com/rancher/norman/types"
    gaccess "github.com/rancher/rancher/pkg/api/norman/customization/globalnamespaceaccess"
    v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
    mgmtv3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
)

type FaaWrapper struct {
    Users     mgmtv3.UserInterface
    GrbLister mgmtv3.GlobalRoleBindingLister
    GrLister  mgmtv3.GlobalRoleLister
}

// 给 rancher api 添加事件,如果这里没有添加 echo1 按钮是不能用的
// echo1 要与 schema 中的 MustImportAndCustomize 中 types.Action -> input ->  key 对应
func (w *FaaWrapper) Formatter(request *types.APIContext, resource *types.RawResource) {
    resource.AddAction(request, "echo1")
    // 在数据 data 中的 links 里面添加一个新的 url; 如图 4-1
    resource.Links["aaa"] = "bbb"
}

// 用户使用 action 按钮功能后触发这个函数。本例中的 echo1 按钮
func (w *FaaWrapper) ActionHandler(actionName string, action *types.Action, request *types.APIContext) error {
    callerID := request.Request.Header.Get(gaccess.ImpersonateUserHeader)
    ma := gaccess.MemberAccess{
        Users:     w.Users,
        GrbLister: w.GrbLister,
        GrLister:  w.GrLister,
    }
    canAccess, err := ma.IsAdmin(callerID)
    if err != nil {
        return err
    }
    if !canAccess {
        return fmt.Errorf("aaaaa")
    }

    switch actionName {
    case "echo1":
        bytes, _ := json.Marshal(v3.FaaStatus{Msg1: "hello11"})
        request.Respons.Write(bytes)
        return nil
    default:
        return fmt.Errorf("bbbbbb")
    }
}

4-1

5. norman 中引入 4 中实现的功能

将 4 中定义的一些按钮 Formatter 和 ActionHandler 的动作引入到 schema 中
目录 /pkg/api/norman/server/managementstored/setup.go

修改 /pkg/api/norman/server/managementstored/setup.go
func Setup(ctx context.Context, apiContext *config.ScaledContext, clusterManager *clustermanager.Manager,
    k8sProxy http.Handler, localClusterEnabled bool) error {
    // Here we setup all types that will be stored in the Management cluster
    schemas := apiContext.Schemas

    factory := &crd.Factory{ClientGetter: apiContext.ClientGetter}

    factory.BatchCreateCRDs(ctx, config.ManagementStorageContext, scheme.Scheme, schemas, &managementschema.Version,
        client.AuthConfigType,
        // ............
        client.ClusterTemplateRevisionType,
        // 这里是 rancher generate 生成的 faa 文件中的 FaaType,加载自定义 CRD
        client.FaaType,
    )

    // ............

    if err := factory.BatchWait(); err != nil {
        return err
    }

    Clusters(ctx, schemas, apiContext, clusterManager, k8sProxy)
    // ............
    ClusterScans(schemas, apiContext, clusterManager)
    // 调用下面定义的 Faa 函数
    Faa(schemas, apiContext)

    if err := NodeTypes(schemas, apiContext); err != nil {
        return err
    }

    authapi.Setup(ctx, clusterrouter.GetClusterID, apiContext, schemas)
    authn.SetRTBStore(ctx, schemas.Schema(&managementschema.Version, client.ClusterRoleTemplateBindingType), apiContext)
    authn.SetRTBStore(ctx, schemas.Schema(&managementschema.Version, client.ProjectRoleTemplateBindingType), apiContext)
    nodeStore.SetupStore(schemas.Schema(&managementschema.Version, client.NodeType))
    projectaction.SetProjectStore(schemas.Schema(&managementschema.Version, client.ProjectType), apiContext)
    setupScopedTypes(schemas)
    setupPasswordTypes(ctx, schemas, apiContext)

    multiclusterapp.SetMemberStore(ctx, schemas.Schema(&managementschema.Version, client.MultiClusterAppType), apiContext)
    GlobalDNSProvidersPwdWrap(schemas, apiContext, localClusterEnabled)

    return nil
}

func Faa(schemas *types.Schemas, management *config.ScaledContext) {
    schema := schemas.Schema(&managementschema.Version, client.FaaType)

    // wrapper 变量就是在初始化 FaaWrapper 中的 User 等信息
    wrapper := faa.FaaWrapper{
        Users:     management.Management.Users(""),
        GrbLister: management.Management.GlobalRoleBindings("").Controller().Lister(),
        GrLister:  management.Management.GlobalRoles("").Controller().Lister(),
    }

    // 定义按钮提交后要执行的功能函数,这里调用的就是 4 中的 ActionHandler 函数
    schema.ActionHandler = wrapper.ActionHandler

    // 引入 4 中定义的 FaaWrapper,在定义 api type 为 resource 时的 action 名,绿色按钮部分
    schema.Formatter = wrapper.Formatter
        
    // 还可以 schema.CollectionFormatter 定义在 api type 为 collection 时引入的 action 功能

    schema.Store = namespacedresource.Wrap(schema.Store, management.Core.Namespaces(""), namespace.GlobalNamespace)
}

6. 重新运行 rancher 就可以看到新的 API 了

相关文章

网友评论

      本文标题:Rancher 二次开发之自定义创建 API

      本文链接:https://www.haomeiwen.com/subject/dwkucdtx.html