kubectl explain should work for both cluster and namespace resources and without a GET method

Kubernetes-commit: 7f4c187ab26fc8b96a091068ee9c84fa447b9291
This commit is contained in:
Filip Křepinský 2023-06-26 18:39:28 +02:00 committed by Kubernetes Publisher
parent 156d5a9a97
commit 138a1cc87a
4 changed files with 9230 additions and 4 deletions

View File

@ -185,6 +185,9 @@ func WithBuiltinTemplateFuncs(tmpl *template.Template) *template.Template {
return copyDict, nil
},
"list": func(values ...any) ([]any, error) {
return values, nil
},
"add": func(value, operand int) int {
return value + operand
},

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,33 @@
{{- $prefix := (ternary "/api" (join "" "/apis/" $.GVR.Group) (not $.GVR.Group)) -}}
{{- /* Search both cluster-scoped and namespaced-scoped paths for the GVR to find its GVK */ -}}
{{- /* Looks for path /apis/<group>/<version>/<resource> or /apis/<group>/<version>/<version>/namespaces/{namespace}/<resource> */ -}}
{{- /* Also search for paths with {name} component in case the list path is missing */ -}}
{{- /* Looks for the following paths: */ -}}
{{- /* /apis/<group>/<version>/<resource> */ -}}
{{- /* /apis/<group>/<version>/<resource>/{name} */ -}}
{{- /* /apis/<group>/<version>/namespaces/{namespace}/<resource> */ -}}
{{- /* /apis/<group>/<version>/namespaces/{namespace}/<resource>/{name} */ -}}
{{- /* Also search for get verb paths in case list verb is missing */ -}}
{{- $clusterScopedSearchPath := join "/" $prefix $.GVR.Version $.GVR.Resource -}}
{{- $namespaceScopedSearchPath := join "/" $prefix $.GVR.Version "namespaces" "\\{namespace\\}" $.GVR.Resource -}}
{{- $path := or (index $.Document "paths" $clusterScopedSearchPath) (index $.Document "paths" $clusterScopedSearchPath) -}}
{{- $clusterScopedNameSearchPath := join "/" $prefix $.GVR.Version $.GVR.Resource "{name}" -}}
{{- $namespaceScopedSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource -}}
{{- $namespaceScopedNameSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource "{name}" -}}
{{- $gvk := "" -}}
{{- /* Pull GVK from operation */ -}}
{{- with $gvk := and $path (index $path "get" "x-kubernetes-group-version-kind") -}}
{{- range $index, $searchPath := (list $clusterScopedSearchPath $clusterScopedNameSearchPath $namespaceScopedSearchPath $namespaceScopedNameSearchPath) -}}
{{- with $resourcePathElement := index $.Document "paths" $searchPath -}}
{{- range $methodIndex, $method := (list "get" "post" "put" "patch" "delete") -}}
{{- with $resourceMethodPathElement := index $resourcePathElement $method -}}
{{- with $gvk = index $resourceMethodPathElement "x-kubernetes-group-version-kind" -}}
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- with $gvk -}}
{{- if $gvk.group -}}
GROUP: {{ $gvk.group }}{{"\n" -}}
{{- end -}}

View File

@ -41,17 +41,46 @@ var (
//go:embed apiextensions.k8s.io_v1.json
apiextensionsJSON string
//go:embed batch.k8s.io_v1.json
batchJSON string
apiExtensionsV1OpenAPI map[string]interface{} = func() map[string]interface{} {
var res map[string]interface{}
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
return res
}()
apiExtensionsV1OpenAPIWithoutListVerb map[string]interface{} = func() map[string]interface{} {
var res map[string]interface{}
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
paths := res["paths"].(map[string]interface{})
delete(paths, "/apis/apiextensions.k8s.io/v1/customresourcedefinitions")
return res
}()
apiExtensionsV1OpenAPISpec spec3.OpenAPI = func() spec3.OpenAPI {
var res spec3.OpenAPI
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
return res
}()
batchV1OpenAPI map[string]interface{} = func() map[string]interface{} {
var res map[string]interface{}
utilruntime.Must(json.Unmarshal([]byte(batchJSON), &res))
return res
}()
batchV1OpenAPIWithoutListVerb map[string]interface{} = func() map[string]interface{} {
var res map[string]interface{}
utilruntime.Must(json.Unmarshal([]byte(batchJSON), &res))
paths := res["paths"].(map[string]interface{})
delete(paths, "/apis/batch/v1/jobs")
delete(paths, "/apis/batch/v1/namespaces/{namespace}/jobs")
delete(paths, "/apis/batch/v1/cronjobs")
delete(paths, "/apis/batch/v1/namespaces/{namespace}/cronjobs/{name}")
return res
}()
)
type testCase struct {
@ -143,6 +172,74 @@ func TestPlaintext(t *testing.T) {
checkContains("CustomResourceDefinition represents a resource that should be exposed"),
},
},
{
// Test basic ability to find a namespaced GVR and print its description
Name: "SchemaFoundNamespaced",
Context: v2.TemplateContext{
Document: batchV1OpenAPI,
GVR: schema.GroupVersionResource{
Group: "batch",
Version: "v1",
Resource: "jobs",
},
FieldPath: nil,
Recursive: false,
},
Checks: []check{
checkContains("Job represents the configuration of a single job"),
},
},
{
// Test basic ability to find a GVR without a list verb and print its description
Name: "SchemaFoundWithoutListVerb",
Context: v2.TemplateContext{
Document: apiExtensionsV1OpenAPIWithoutListVerb,
GVR: schema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Version: "v1",
Resource: "customresourcedefinitions",
},
FieldPath: nil,
Recursive: false,
},
Checks: []check{
checkContains("CustomResourceDefinition represents a resource that should be exposed"),
},
},
{
// Test basic ability to find a namespaced GVR without a list verb and print its description
Name: "SchemaFoundNamespacedWithoutListVerb",
Context: v2.TemplateContext{
Document: batchV1OpenAPIWithoutListVerb,
GVR: schema.GroupVersionResource{
Group: "batch",
Version: "v1",
Resource: "jobs",
},
FieldPath: nil,
Recursive: false,
},
Checks: []check{
checkContains("Job represents the configuration of a single job"),
},
},
{
// Test basic ability to find a namespaced GVR without a top level list verb and print its description
Name: "SchemaFoundNamespacedWithoutTopLevelListVerb",
Context: v2.TemplateContext{
Document: batchV1OpenAPIWithoutListVerb,
GVR: schema.GroupVersionResource{
Group: "batch",
Version: "v1",
Resource: "cronjobs",
},
FieldPath: nil,
Recursive: false,
},
Checks: []check{
checkContains("CronJob represents the configuration of a single cron job"),
},
},
{
// Test that shows trying to find a non-existent field path of an existing
// schema