本次最终目的是在 rancher 内创建一个新的 API。
最终效果如下图
![](https://img.haomeiwen.com/i11480804/7af6eac37d344f78.png)
![](https://img.haomeiwen.com/i11480804/478890df91f7f693.png)
![](https://img.haomeiwen.com/i11480804/859f096c69c572c3.png)
相关目录结构会在开发过程中介绍。
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
是用于自动生成在内部和外部类型之间转换的函数的工具。
一般的转换代码生成任务涉及三套程序包:
- 一套包含内部类型的程序包;
- 一套包含外部类型的程序包;
- 单个目标程序包(即,生成的转换函数所在的位置,以及开发人员授权的转换功能所在的位置);
参数 | 说明 |
---|---|
// +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 的名字,如下图
![](https://img.haomeiwen.com/i11480804/5d0e9c66d4b1c068.png)
18~25
定义 data 中的两个返回数据的结构体,结果如下图。
关于第 20 行 norman 的规范参考这里
![](https://img.haomeiwen.com/i11480804/f71a897edc74dea7.png)
28~31
定义一个 Action 操作以及操作中的 input 内容,如下图
![](https://img.haomeiwen.com/i11480804/b2caa912b890f9fb.png)
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",
},
}
})
}
代码说明。看注释
![](https://img.haomeiwen.com/i11480804/83f448002da754d5.png)
![](https://img.haomeiwen.com/i11480804/b61ad2b0b9099eec.png)
![](https://img.haomeiwen.com/i11480804/5aa2bc1d23e9aa6b.png)
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")
}
}
![](https://img.haomeiwen.com/i11480804/008df29f816bd4ac.png)
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)
}
网友评论