我有一个与 k8s 对话的 golang 后端。我想重新制定从 k8s 获得的错误响应并将其发送到前端。
我想为用户返回一个有意义的验证错误消息,当他添加一个无效的名称时,一些东西已经存在......
我想要一些通用的东西,而不是在每个端点的控制器中硬编码。
我正在使用
kubernetes/client-go
.
例如假设我想添加一个酒店到
etcd
,当我尝试添加酒店名称:hotel123时,它已经存在。
\"hotel123\" already exists
.hotel123 already exists
.例如假设我想在
etcd
中添加酒店,当我尝试添加酒店名称:hotel_123时,这已经存在了。
\"hotel_123\" is invalid: metadata.name: Invalid value: \"hotel_123\": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"
.hotel_123 is invalid
如何返回自定义用户友好的错误消息?
PS:我有多个函数,所以验证应该是通用的。
一般来说(虽然有变通方法),如果你想捕获错误以返回更有用的错误,你要确保满足以下条件:
在下面的例子中,我试图读取一个不存在的配置文件。我的代码检查返回的错误是
fs.PathError
然后抛出它自己更有用的错误。您可以将这个总体思路扩展到您的用例。
package main
import (
"errors"
"fmt"
"io/fs"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
var myError error
config, originalError := clientcmd.BuildConfigFromFlags("", "/some/path/that/doesnt/exist")
if originalError != nil {
var pathError *fs.PathError
switch {
case errors.As(originalError, &pathError):
myError = fmt.Errorf("there is no config file at %s", originalError.(*fs.PathError).Path)
default:
myError = fmt.Errorf("there was an error and it's type was %T", originalError)
}
fmt.Printf("%#v", myError)
} else {
fmt.Println("There was no error")
fmt.Println(config)
}
}
在你的调试中,你会发现
%T
格式化程序很有用。
这里是 playground 上的另一个例子,使用更通用的案例,因为 playground 只允许你使用标准库。
String
err.Error()
是您可以从 Kubernetes 服务器为用户获取的原始的、有意义的和最好的错误消息(或者您必须自己翻译它)。
说明:
你需要超越
kubernetes/client-go
客户端库的表面。
每个客户端通过 HTTP REST APIs 与 k8s 服务器对话,它在
json
中发送回响应。如果可能的话,它是 client-go
库解码响应主体并将结果存储到对象中。
至于你的情况,让我通过
Namespace
资源给你一些例子:
POST https://xxx.xx.xx.xx:6443/api/v1/namespaces?fieldManager=kubectl-create
Response Status: 409 Conflict
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "namespaces \"hotel123\" already exists",
"reason": "AlreadyExists",
"details": {
"name": "hotel123",
"kind": "namespaces"
},
"code": 409
}
POST https://xxx.xx.xx.xx:6443/api/v1/namespaces?fieldManager=kubectl-create
Response Status: 422 Unprocessable Entity
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Namespace \"hotel_123\" is invalid: metadata.name: Invalid value: \"hotel_123\": a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]\r\n([-a-z0-9]*[a-z0-9])?')",
"reason": "Invalid",
"details": {
"name": "hotel_123",
"kind": "Namespace",
"causes": [
{
"reason": "FieldValueInvalid",
"message": "Invalid value: \"hotel_123\": a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')",
"field": "metadata.name"
}
]
},
"code": 422
}
POST https://xxx.xx.xx.xx:6443/api/v1/namespaces?fieldManager=kubectl-create
Response Status: 201 Created
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "hotel12345",
"uid": "7a301d8b-37cd-45a5-8345-82wsufy88223456",
"resourceVersion": "12233445566",
"creationTimestamp": "2023-04-03T15:35:59Z",
"managedFields": [
{
"manager": "kubectl-create",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-04-03T15:35:59Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:status": {
"f:phase": {}
}
}
}
]
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
一句话,如果HTTP Status不是2xx,返回的对象是Status类型,并且有.Status != StatusSuccess,Status中的附加信息(
message
)将被用来丰富错误 ,就像下面的代码片段:
createdNamespace, err := clientset.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{})
if err != nil {
// print "namespaces \"hotel123\" already exists" or so
fmt.Println(err.Error())
return err.Error()
}
fmt.Printf("Created Namespace %+v in the cluster\n", createdNamespace)
return ""