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