kubectl explain should work for both cluster and namespace resources and without a GET method
Kubernetes-commit: 7f4c187ab26fc8b96a091068ee9c84fa447b9291
This commit is contained in:
parent
156d5a9a97
commit
138a1cc87a
|
@ -185,6 +185,9 @@ func WithBuiltinTemplateFuncs(tmpl *template.Template) *template.Template {
|
||||||
|
|
||||||
return copyDict, nil
|
return copyDict, nil
|
||||||
},
|
},
|
||||||
|
"list": func(values ...any) ([]any, error) {
|
||||||
|
return values, nil
|
||||||
|
},
|
||||||
"add": func(value, operand int) int {
|
"add": func(value, operand int) int {
|
||||||
return value + operand
|
return value + operand
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,13 +2,33 @@
|
||||||
{{- $prefix := (ternary "/api" (join "" "/apis/" $.GVR.Group) (not $.GVR.Group)) -}}
|
{{- $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 */ -}}
|
{{- /* 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 -}}
|
{{- $clusterScopedSearchPath := join "/" $prefix $.GVR.Version $.GVR.Resource -}}
|
||||||
{{- $namespaceScopedSearchPath := join "/" $prefix $.GVR.Version "namespaces" "\\{namespace\\}" $.GVR.Resource -}}
|
{{- $clusterScopedNameSearchPath := join "/" $prefix $.GVR.Version $.GVR.Resource "{name}" -}}
|
||||||
{{- $path := or (index $.Document "paths" $clusterScopedSearchPath) (index $.Document "paths" $clusterScopedSearchPath) -}}
|
{{- $namespaceScopedSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource -}}
|
||||||
|
{{- $namespaceScopedNameSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource "{name}" -}}
|
||||||
|
{{- $gvk := "" -}}
|
||||||
|
|
||||||
{{- /* Pull GVK from operation */ -}}
|
{{- /* 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 -}}
|
{{- if $gvk.group -}}
|
||||||
GROUP: {{ $gvk.group }}{{"\n" -}}
|
GROUP: {{ $gvk.group }}{{"\n" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
|
@ -41,17 +41,46 @@ var (
|
||||||
//go:embed apiextensions.k8s.io_v1.json
|
//go:embed apiextensions.k8s.io_v1.json
|
||||||
apiextensionsJSON string
|
apiextensionsJSON string
|
||||||
|
|
||||||
|
//go:embed batch.k8s.io_v1.json
|
||||||
|
batchJSON string
|
||||||
|
|
||||||
apiExtensionsV1OpenAPI map[string]interface{} = func() map[string]interface{} {
|
apiExtensionsV1OpenAPI map[string]interface{} = func() map[string]interface{} {
|
||||||
var res map[string]interface{}
|
var res map[string]interface{}
|
||||||
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
|
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
|
||||||
return 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 {
|
apiExtensionsV1OpenAPISpec spec3.OpenAPI = func() spec3.OpenAPI {
|
||||||
var res spec3.OpenAPI
|
var res spec3.OpenAPI
|
||||||
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
|
utilruntime.Must(json.Unmarshal([]byte(apiextensionsJSON), &res))
|
||||||
return 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 {
|
type testCase struct {
|
||||||
|
@ -143,6 +172,74 @@ func TestPlaintext(t *testing.T) {
|
||||||
checkContains("CustomResourceDefinition represents a resource that should be exposed"),
|
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
|
// Test that shows trying to find a non-existent field path of an existing
|
||||||
// schema
|
// schema
|
||||||
|
|
Loading…
Reference in New Issue