kubectl explain should work for both cluster and namespace resources and without a GET method
This commit is contained in:
		@@ -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
											
										
									
								
							@@ -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 -}}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user