...
1{{- /* Determine if Path for requested GVR is at /api or /apis based on emptiness of group */ -}}
2{{- $prefix := (ternary "/api" (join "" "/apis/" $.GVR.Group) (not $.GVR.Group)) -}}
3
4{{- /* Search both cluster-scoped and namespaced-scoped paths for the GVR to find its GVK */ -}}
5{{- /* Also search for paths with {name} component in case the list path is missing */ -}}
6{{- /* Looks for the following paths: */ -}}
7{{- /* /apis/<group>/<version>/<resource> */ -}}
8{{- /* /apis/<group>/<version>/<resource>/{name} */ -}}
9{{- /* /apis/<group>/<version>/namespaces/{namespace}/<resource> */ -}}
10{{- /* /apis/<group>/<version>/namespaces/{namespace}/<resource>/{name} */ -}}
11{{- /* Also search for get verb paths in case list verb is missing */ -}}
12{{- $clusterScopedSearchPath := join "/" $prefix $.GVR.Version $.GVR.Resource -}}
13{{- $clusterScopedNameSearchPath := join "/" $prefix $.GVR.Version $.GVR.Resource "{name}" -}}
14{{- $namespaceScopedSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource -}}
15{{- $namespaceScopedNameSearchPath := join "/" $prefix $.GVR.Version "namespaces" "{namespace}" $.GVR.Resource "{name}" -}}
16{{- $gvk := "" -}}
17
18{{- /* Pull GVK from operation */ -}}
19{{- range $index, $searchPath := (list $clusterScopedSearchPath $clusterScopedNameSearchPath $namespaceScopedSearchPath $namespaceScopedNameSearchPath) -}}
20 {{- with $resourcePathElement := index $.Document "paths" $searchPath -}}
21 {{- range $methodIndex, $method := (list "get" "post" "put" "patch" "delete") -}}
22 {{- with $resourceMethodPathElement := index $resourcePathElement $method -}}
23 {{- with $gvk = index $resourceMethodPathElement "x-kubernetes-group-version-kind" -}}
24 {{- break -}}
25 {{- end -}}
26 {{- end -}}
27 {{- end -}}
28 {{- end -}}
29{{- end -}}
30
31{{- with $gvk -}}
32 {{- if $gvk.group -}}
33 GROUP: {{ $gvk.group }}{{"\n" -}}
34 {{- end -}}
35 KIND: {{ $gvk.kind}}{{"\n" -}}
36 VERSION: {{ $gvk.version }}{{"\n" -}}
37 {{- "\n" -}}
38
39 {{- with include "schema" (dict "gvk" $gvk "Document" $.Document "FieldPath" $.FieldPath "Recursive" $.Recursive) -}}
40 {{- . -}}
41 {{- else -}}
42 {{- throw "error: GVK %v not found in OpenAPI schema" $gvk -}}
43 {{- end -}}
44{{- else -}}
45 {{- throw "error: GVR (%v) not found in OpenAPI schema" $.GVR.String -}}
46{{- end -}}
47{{- "\n" -}}
48
49{{- /*
50Finds a schema with the given GVK and prints its explain output or empty string
51if GVK was not found
52
53Takes dictionary as argument with keys:
54 gvk: openapiv3 JSON schema
55 Document: entire doc
56 FieldPath: field path to follow
57 Recursive: print recursive
58*/ -}}
59{{- define "schema" -}}
60 {{- /* Find definition with this GVK by filtering out the components/schema with the given x-kubernetes-group-version-kind */ -}}
61 {{- range index $.Document "components" "schemas" -}}
62 {{- if contains (index . "x-kubernetes-group-version-kind") $.gvk -}}
63 {{- with include "output" (set $ "schema" .) -}}
64 {{- . -}}
65 {{- else -}}
66 {{- $fieldName := (index $.FieldPath (sub (len $.FieldPath) 1)) -}}
67 {{- throw "error: field \"%v\" does not exist" $fieldName}}
68 {{- end -}}
69 {{- break -}}
70 {{- end -}}
71 {{- end -}}
72{{- end -}}
73
74{{- /*
75Follows FieldPath until the FieldPath is empty. Then prints field name and field
76list of resultant schema. If field path is not found. Prints nothing.
77Example output:
78
79FIELD: spec
80
81DESCRIPTION:
82 <template "description">
83
84FIELDS:
85 <template "fieldList">
86
87Takes dictionary as argument with keys:
88 schema: openapiv3 JSON schema
89 history: map[string]int
90 Document: entire doc
91 FieldPath: field path to follow
92 Recursive: print recursive
93*/ -}}
94{{- define "output" -}}
95 {{- $refString := or (index $.schema "$ref") "" -}}
96 {{- $nextContext := set $ "history" (set $.history $refString 1) -}}
97 {{- $resolved := or (resolveRef $refString $.Document) $.schema -}}
98 {{- if not $.FieldPath -}}
99 DESCRIPTION:{{- "\n" -}}
100 {{- or (include "description" (dict "schema" $resolved "Document" $.Document)) "<empty>" | wrap 76 | indent 4 -}}{{- "\n" -}}
101 {{- with include "fieldList" (dict "schema" $resolved "level" 1 "Document" $.Document "Recursive" $.Recursive) -}}
102 FIELDS:{{- "\n" -}}
103 {{- . -}}
104 {{- end -}}
105 {{- else if and $refString (index $.history $refString) -}}
106 {{- /* Stop and do nothing. Hit a cycle */ -}}
107 {{- else if and $resolved.properties (index $resolved.properties (first $.FieldPath)) -}}
108 {{- /* Schema has this property directly. Traverse to next schema */ -}}
109 {{- $subschema := index $resolved.properties (first $.FieldPath) -}}
110 {{- if eq 1 (len $.FieldPath) -}}
111 {{- /* TODO: The original explain would say RESOURCE instead of FIELD here under some circumstances */ -}}
112 FIELD: {{first $.FieldPath}} <{{ template "typeGuess" (dict "schema" $subschema "Document" $.Document) }}>{{"\n"}}
113 {{- template "extractEnum" (dict "schema" $subschema "Document" $.Document "isLongView" true "limit" -1) -}}{{"\n"}}
114 {{- "\n" -}}
115 {{- end -}}
116
117 {{- template "output" (set $nextContext "history" (dict) "FieldPath" (slice $.FieldPath 1) "schema" $subschema ) -}}
118 {{- else if $resolved.items -}}
119 {{- /* If the schema is an array then traverse the array item fields */ -}}
120 {{- template "output" (set $nextContext "schema" $resolved.items) -}}
121 {{- else if $resolved.additionalProperties -}}
122 {{- /* If the schema is a map then traverse the map item fields */ -}}
123 {{- template "output" (set $nextContext "schema" $resolved.additionalProperties) -}}
124 {{- else -}}
125 {{- /* Last thing to try is all the alternatives in the allOf case */ -}}
126 {{- /* Stop when one of the alternatives has an output at all */ -}}
127 {{- range $index, $subschema := $resolved.allOf -}}
128 {{- with include "output" (set $nextContext "schema" $subschema) -}}
129 {{- . -}}
130 {{- break -}}
131 {{- end -}}
132 {{- end -}}
133 {{- end -}}
134{{- end -}}
135
136{{- /*
137Prints list of fields of a given open api schema in following form:
138
139field1 <type> -required-
140 DESCRIPTION
141
142field2 <type> -required-
143 DESCRIPTION
144
145or if Recursive is enabled:
146field1 <type> -required-
147 subfield1 <type>
148 subsubfield1 <type>
149 subsubfield2 <type>
150 subfield2 <type>
151field2 <type> -required-
152 subfield1 <type>
153 subfield2 <type>
154
155Follows refs for field traversal. If there are cycles in the schema, they are
156detected and traversal ends.
157
158Takes dictionary as argument with keys:
159 schema: openapiv3 JSON schema
160 level: indentation level
161 history: map[string]int containing all ref names so far
162 Document: entire doc
163 Recursive: print recursive
164*/ -}}
165{{- define "fieldList" -}}
166 {{- /* Resolve schema if it is a ref */}}
167 {{- /* If this is a ref seen before, then ignore it */}}
168 {{- $refString := or (index $.schema "$ref") "" }}
169 {{- if and $refString (index (or $.history (dict)) $refString) -}}
170 {{- /* Do nothing for cycle */}}
171 {{- else -}}
172 {{- $nextContext := set $ "history" (set $.history $refString 1) -}}
173 {{- $resolved := or (resolveRef $refString $.Document) $.schema -}}
174 {{- range $k, $v := $resolved.properties -}}
175 {{- template "fieldDetail" (dict "name" $k "schema" $resolved "short" $.Recursive "level" $.level "Document" $.Document) -}}
176 {{- if $.Recursive -}}
177 {{- /* Check if we already know about this element */}}
178 {{- template "fieldList" (set $nextContext "schema" $v "level" (add $.level 1)) -}}
179 {{- end -}}
180 {{- end -}}
181 {{- range $resolved.allOf -}}
182 {{- template "fieldList" (set $nextContext "schema" .)}}
183 {{- end -}}
184 {{- if $resolved.items}}{{- template "fieldList" (set $nextContext "schema" $resolved.items)}}{{end}}
185 {{- if $resolved.additionalProperties}}{{- template "fieldList" (set $nextContext "schema" $resolved.additionalProperties)}}{{end}}
186 {{- end -}}
187{{- end -}}
188
189
190{{- /*
191
192Prints a single field of the given schema
193Optionally prints in a one-line style
194
195Takes dictionary as argument with keys:
196 schema: openapiv3 JSON schema which contains the field
197 name: name of field
198 short: limit printing to a single-line summary
199 level: indentation amount
200 Document: openapi document
201*/ -}}
202{{- define "fieldDetail" -}}
203 {{- $level := or $.level 0 -}}
204 {{- $indentAmount := mul $level 2 -}}
205 {{- $fieldSchema := index $.schema.properties $.name -}}
206 {{- $.name | indent $indentAmount -}}{{"\t"}}<{{ template "typeGuess" (dict "schema" $fieldSchema "Document" $.Document) }}>
207 {{- if contains $.schema.required $.name}} -required-{{- end -}}
208 {{- template "extractEnum" (dict "schema" $fieldSchema "Document" $.Document "isLongView" false "limit" 4 "indentAmount" $indentAmount) -}}
209 {{- "\n" -}}
210 {{- if not $.short -}}
211 {{- or $fieldSchema.description "<no description>" | wrap (sub 78 $indentAmount) | indent (add $indentAmount 2) -}}{{- "\n" -}}
212 {{- "\n" -}}
213 {{- end -}}
214{{- end -}}
215
216{{- /*
217
218Prints the description of the given OpenAPI v3 schema. Also walks through all
219sibling schemas to the provided schema and prints the description of those schemas
220too
221
222Takes dictionary as argument with keys:
223 schema: openapiv3 JSON schema
224 Document: document to resolve refs within
225*/ -}}
226{{- define "description" -}}
227 {{- with or (resolveRef (index $.schema "$ref") $.Document) $.schema -}}
228 {{- if .description -}}
229 {{- .description -}}
230 {{- "\n" -}}
231 {{- end -}}
232 {{- range .allOf -}}
233 {{- template "description" (set $ "schema" .)}}
234 {{- end -}}
235 {{- if .items -}}
236 {{- template "description" (set $ "schema" .items) -}}
237 {{- end -}}
238 {{- if .additionalProperties -}}
239 {{- template "description" (set $ "schema" .additionalProperties) -}}
240 {{- end -}}
241 {{- end -}}
242{{- end -}}
243
244{{- /* Renders a shortstring representing an interpretation of what is the "type"
245 of a subschema e.g.:
246
247 `string` `number`, `Object`, `[]Object`, `map[string]string`, etc.
248
249 Serves as a more helpful type hint than raw typical openapi `type` field
250
251Takes dictionary as argument with keys:
252 schema: openapiv3 JSON schema
253 Document: openapi document
254*/ -}}
255{{- define "typeGuess" -}}
256 {{- with $.schema -}}
257 {{- if .items -}}
258 []{{template "typeGuess" (set $ "schema" .items)}}
259 {{- else if .additionalProperties -}}
260 map[string]{{template "typeGuess" (set $ "schema" .additionalProperties)}}
261 {{- else if and .allOf (not .properties) (eq (len .allOf) 1) -}}
262 {{- /* If allOf has a single element and there are no direct
263 properties on the schema, defer to the allOf */ -}}
264 {{- template "typeGuess" (set $ "schema" (first .allOf)) -}}
265 {{- else if index . "$ref"}}
266 {{- /* Parse the #!/components/schemas/io.k8s.Kind string into just the Kind name */ -}}
267 {{- $ref := index . "$ref" -}}
268 {{- /* Look up ref schema to see primitive type. Only put the ref type name if it is an object. */ -}}
269 {{- $refSchema := resolveRef $ref $.Document -}}
270 {{- if (or (not $refSchema) (or (not $refSchema.type) (eq $refSchema.type "object"))) -}}
271 {{- $name := split $ref "/" | last -}}
272 {{- or (split $name "." | last) "Object" -}}
273 {{- else if $refSchema.type -}}
274 {{- or $refSchema.type "Object" -}}
275 {{- else -}}
276 {{- or .type "Object" -}}
277 {{- end -}}
278 {{- else -}}
279 {{/* Old explain used capitalized "Object". Just follow suit */}}
280 {{- if eq .type "object" -}}Object
281 {{- else -}}{{- or .type "Object" -}}{{- end -}}
282 {{- end -}}
283 {{- else -}}
284 {{- fail "expected schema argument to subtemplate 'typeguess'" -}}
285 {{- end -}}
286{{- end -}}
287
288{{- /* Check if there is any enum returns it in this format e.g.:
289
290 ENUM: "", BlockDevice, CharDevice, Directory
291 enum: "", BlockDevice, CharDevice, Directory
292
293 Can change the style of enum in future by modifying this function
294
295Takes dictionary as argument with keys:
296 schema: openapiv3 JSON schema
297 Document: openapi document
298 isLongView: (boolean) Simple view: long list of all fields. Long view: all details of one field
299 limit: (int) truncate the amount of enums that can be printed in simple view, -1 means all items
300 indentAmount: intent of the beginning enum line in longform view
301*/ -}}
302{{- define "extractEnum" -}}
303 {{- with $.schema -}}
304 {{- if .enum -}}
305 {{- $enumLen := len .enum -}}
306 {{- $limit := or $.limit -1 -}}
307 {{- if eq $.isLongView false -}}
308 {{- "\n" -}}
309 {{- "" | indent $.indentAmount -}}
310 {{- "enum: " -}}
311 {{- else -}}
312 {{- "ENUM:" -}}
313 {{- end -}}
314 {{- range $index, $element := .enum -}}
315 {{- /* Prints , .... and return the range when it goes over the limit */ -}}
316 {{- if and (gt $limit -1) (ge $index $limit) -}}
317 {{- ", ...." -}}
318 {{- break -}}
319 {{- end -}}
320 {{- /* Use to reflect "" when we see empty string */ -}}
321 {{- $elementType := printf "%T" $element -}}
322 {{- /* Print out either `, ` or `\n ` based of the view */ -}}
323 {{- /* Simple view */ -}}
324 {{- if and (gt $index 0) (eq $.isLongView false) -}}
325 {{- ", " -}}
326 {{- /* Long view */ -}}
327 {{- else if eq $.isLongView true -}}
328 {{- "\n" -}}{{- "" | indent 4 -}}
329 {{- end -}}
330 {{- /* Convert empty string to `""` for more clarification */ -}}
331 {{- if and (eq "string" $elementType) (eq $element "") -}}
332 {{- `""` -}}
333 {{- else -}}
334 {{- $element -}}
335 {{- end -}}
336 {{- end -}}
337 {{- end -}}
338 {{- end -}}
339{{- end -}}
View as plain text