1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package util
18
19import (
20	"errors"
21	"fmt"
22	"strings"
23
24	"k8s.io/gengo/types"
25)
26
27var supportedTags = []string{
28	"genclient",
29	"genclient:nonNamespaced",
30	"genclient:noVerbs",
31	"genclient:onlyVerbs",
32	"genclient:skipVerbs",
33	"genclient:noStatus",
34	"genclient:readonly",
35	"genclient:method",
36}
37
38// SupportedVerbs is a list of supported verbs for +onlyVerbs and +skipVerbs.
39var SupportedVerbs = []string{
40	"create",
41	"update",
42	"updateStatus",
43	"delete",
44	"deleteCollection",
45	"get",
46	"list",
47	"watch",
48	"patch",
49	"apply",
50	"applyStatus",
51}
52
53// ReadonlyVerbs represents a list of read-only verbs.
54var ReadonlyVerbs = []string{
55	"get",
56	"list",
57	"watch",
58}
59
60// genClientPrefix is the default prefix for all genclient tags.
61const genClientPrefix = "genclient:"
62
63// unsupportedExtensionVerbs is a list of verbs we don't support generating
64// extension client functions for.
65var unsupportedExtensionVerbs = []string{
66	"updateStatus",
67	"deleteCollection",
68	"watch",
69	"delete",
70}
71
72// inputTypeSupportedVerbs is a list of verb types that supports overriding the
73// input argument type.
74var inputTypeSupportedVerbs = []string{
75	"create",
76	"update",
77	"apply",
78}
79
80// resultTypeSupportedVerbs is a list of verb types that supports overriding the
81// resulting type.
82var resultTypeSupportedVerbs = []string{
83	"create",
84	"update",
85	"get",
86	"list",
87	"patch",
88	"apply",
89}
90
91// Extensions allows to extend the default set of client verbs
92// (CRUD+watch+patch+list+deleteCollection) for a given type with custom defined
93// verbs. Custom verbs can have custom input and result types and also allow to
94// use a sub-resource in a request instead of top-level resource type.
95//
96// Example:
97//
98// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale
99//
100// type ReplicaSet struct { ... }
101//
102// The 'method=UpdateScale' is the name of the client function.
103// The 'verb=update' here means the client function will use 'PUT' action.
104// The 'subresource=scale' means we will use SubResource template to generate this client function.
105// The 'input' is the input type used for creation (function argument).
106// The 'result' (not needed in this case) is the result type returned from the
107// client function.
108//
109type extension struct {
110	// VerbName is the name of the custom verb (Scale, Instantiate, etc..)
111	VerbName string
112	// VerbType is the type of the verb (only verbs from SupportedVerbs are
113	// supported)
114	VerbType string
115	// SubResourcePath defines a path to a sub-resource to use in the request.
116	// (optional)
117	SubResourcePath string
118	// InputTypeOverride overrides the input parameter type for the verb. By
119	// default the original type is used. Overriding the input type only works for
120	// "create" and "update" verb types. The given type must exists in the same
121	// package as the original type.
122	// (optional)
123	InputTypeOverride string
124	// ResultTypeOverride overrides the resulting object type for the verb. By
125	// default the original type is used. Overriding the result type works.
126	// (optional)
127	ResultTypeOverride string
128}
129
130// IsSubresource indicates if this extension should generate the sub-resource.
131func (e *extension) IsSubresource() bool {
132	return len(e.SubResourcePath) > 0
133}
134
135// HasVerb checks if the extension matches the given verb.
136func (e *extension) HasVerb(verb string) bool {
137	return e.VerbType == verb
138}
139
140// Input returns the input override package path and the type.
141func (e *extension) Input() (string, string) {
142	parts := strings.Split(e.InputTypeOverride, ".")
143	return parts[len(parts)-1], strings.Join(parts[0:len(parts)-1], ".")
144}
145
146// Result returns the result override package path and the type.
147func (e *extension) Result() (string, string) {
148	parts := strings.Split(e.ResultTypeOverride, ".")
149	return parts[len(parts)-1], strings.Join(parts[0:len(parts)-1], ".")
150}
151
152// Tags represents a genclient configuration for a single type.
153type Tags struct {
154	// +genclient
155	GenerateClient bool
156	// +genclient:nonNamespaced
157	NonNamespaced bool
158	// +genclient:noStatus
159	NoStatus bool
160	// +genclient:noVerbs
161	NoVerbs bool
162	// +genclient:skipVerbs=get,update
163	// +genclient:onlyVerbs=create,delete
164	SkipVerbs []string
165	// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale
166	Extensions []extension
167}
168
169// HasVerb returns true if we should include the given verb in final client interface and
170// generate the function for it.
171func (t Tags) HasVerb(verb string) bool {
172	if len(t.SkipVerbs) == 0 {
173		return true
174	}
175	for _, s := range t.SkipVerbs {
176		if verb == s {
177			return false
178		}
179	}
180	return true
181}
182
183// MustParseClientGenTags calls ParseClientGenTags but instead of returning error it panics.
184func MustParseClientGenTags(lines []string) Tags {
185	tags, err := ParseClientGenTags(lines)
186	if err != nil {
187		panic(err.Error())
188	}
189	return tags
190}
191
192// ParseClientGenTags parse the provided genclient tags and validates that no unknown
193// tags are provided.
194func ParseClientGenTags(lines []string) (Tags, error) {
195	ret := Tags{}
196	values := types.ExtractCommentTags("+", lines)
197	var value []string
198	value, ret.GenerateClient = values["genclient"]
199	// Check the old format and error when used to avoid generating client when //+genclient=false
200	if len(value) > 0 && len(value[0]) > 0 {
201		return ret, fmt.Errorf("+genclient=%s is invalid, use //+genclient if you want to generate client or omit it when you want to disable generation", value)
202	}
203	_, ret.NonNamespaced = values[genClientPrefix+"nonNamespaced"]
204	// Check the old format and error when used
205	if value := values["nonNamespaced"]; len(value) > 0 && len(value[0]) > 0 {
206		return ret, fmt.Errorf("+nonNamespaced=%s is invalid, use //+genclient:nonNamespaced instead", value[0])
207	}
208	_, ret.NoVerbs = values[genClientPrefix+"noVerbs"]
209	_, ret.NoStatus = values[genClientPrefix+"noStatus"]
210	onlyVerbs := []string{}
211	if _, isReadonly := values[genClientPrefix+"readonly"]; isReadonly {
212		onlyVerbs = ReadonlyVerbs
213	}
214	// Check the old format and error when used
215	if value := values["readonly"]; len(value) > 0 && len(value[0]) > 0 {
216		return ret, fmt.Errorf("+readonly=%s is invalid, use //+genclient:readonly instead", value[0])
217	}
218	if v, exists := values[genClientPrefix+"skipVerbs"]; exists {
219		ret.SkipVerbs = strings.Split(v[0], ",")
220	}
221	if v, exists := values[genClientPrefix+"onlyVerbs"]; exists || len(onlyVerbs) > 0 {
222		if len(v) > 0 {
223			onlyVerbs = append(onlyVerbs, strings.Split(v[0], ",")...)
224		}
225		skipVerbs := []string{}
226		for _, m := range SupportedVerbs {
227			skip := true
228			for _, o := range onlyVerbs {
229				if o == m {
230					skip = false
231					break
232				}
233			}
234			// Check for conflicts
235			for _, v := range skipVerbs {
236				if v == m {
237					return ret, fmt.Errorf("verb %q used both in genclient:skipVerbs and genclient:onlyVerbs", v)
238				}
239			}
240			if skip {
241				skipVerbs = append(skipVerbs, m)
242			}
243		}
244		ret.SkipVerbs = skipVerbs
245	}
246	var err error
247	if ret.Extensions, err = parseClientExtensions(values); err != nil {
248		return ret, err
249	}
250	return ret, validateClientGenTags(values)
251}
252
253func parseClientExtensions(tags map[string][]string) ([]extension, error) {
254	var ret []extension
255	for name, values := range tags {
256		if !strings.HasPrefix(name, genClientPrefix+"method") {
257			continue
258		}
259		for _, value := range values {
260			// the value comes in this form: "Foo,verb=create"
261			ext := extension{}
262			parts := strings.Split(value, ",")
263			if len(parts) == 0 {
264				return nil, fmt.Errorf("invalid of empty extension verb name: %q", value)
265			}
266			// The first part represents the name of the extension
267			ext.VerbName = parts[0]
268			if len(ext.VerbName) == 0 {
269				return nil, fmt.Errorf("must specify a verb name (// +genclient:method=Foo,verb=create)")
270			}
271			// Parse rest of the arguments
272			params := parts[1:]
273			for _, p := range params {
274				parts := strings.Split(p, "=")
275				if len(parts) != 2 {
276					return nil, fmt.Errorf("invalid extension tag specification %q", p)
277				}
278				key, val := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
279				if len(val) == 0 {
280					return nil, fmt.Errorf("empty value of %q for %q extension", key, ext.VerbName)
281				}
282				switch key {
283				case "verb":
284					ext.VerbType = val
285				case "subresource":
286					ext.SubResourcePath = val
287				case "input":
288					ext.InputTypeOverride = val
289				case "result":
290					ext.ResultTypeOverride = val
291				default:
292					return nil, fmt.Errorf("unknown extension configuration key %q", key)
293				}
294			}
295			// Validate resulting extension configuration
296			if len(ext.VerbType) == 0 {
297				return nil, fmt.Errorf("verb type must be specified (use '// +genclient:method=%s,verb=create')", ext.VerbName)
298			}
299			if len(ext.ResultTypeOverride) > 0 {
300				supported := false
301				for _, v := range resultTypeSupportedVerbs {
302					if ext.VerbType == v {
303						supported = true
304						break
305					}
306				}
307				if !supported {
308					return nil, fmt.Errorf("%s: result type is not supported for %q verbs (supported verbs: %#v)", ext.VerbName, ext.VerbType, resultTypeSupportedVerbs)
309				}
310			}
311			if len(ext.InputTypeOverride) > 0 {
312				supported := false
313				for _, v := range inputTypeSupportedVerbs {
314					if ext.VerbType == v {
315						supported = true
316						break
317					}
318				}
319				if !supported {
320					return nil, fmt.Errorf("%s: input type is not supported for %q verbs (supported verbs: %#v)", ext.VerbName, ext.VerbType, inputTypeSupportedVerbs)
321				}
322			}
323			for _, t := range unsupportedExtensionVerbs {
324				if ext.VerbType == t {
325					return nil, fmt.Errorf("verb %q is not supported by extension generator", ext.VerbType)
326				}
327			}
328			ret = append(ret, ext)
329		}
330	}
331	return ret, nil
332}
333
334// validateTags validates that only supported genclient tags were provided.
335func validateClientGenTags(values map[string][]string) error {
336	for _, k := range supportedTags {
337		delete(values, k)
338	}
339	for key := range values {
340		if strings.HasPrefix(key, strings.TrimSuffix(genClientPrefix, ":")) {
341			return errors.New("unknown tag detected: " + key)
342		}
343	}
344	return nil
345}
346