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