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