blob: befe38db24860362556b1dadba31dc2a2058a6a5 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2018 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 generators
18
19import (
20 "fmt"
21 "sort"
22 "strings"
23
24 "k8s.io/gengo/examples/set-gen/sets"
25 "k8s.io/gengo/types"
26)
27
28const extensionPrefix = "x-kubernetes-"
29
30// extensionAttributes encapsulates common traits for particular extensions.
31type extensionAttributes struct {
32 xName string
33 kind types.Kind
34 allowedValues sets.String
35}
36
37// Extension tag to openapi extension attributes
38var tagToExtension = map[string]extensionAttributes{
39 "patchMergeKey": extensionAttributes{
40 xName: "x-kubernetes-patch-merge-key",
41 kind: types.Slice,
42 },
43 "patchStrategy": extensionAttributes{
44 xName: "x-kubernetes-patch-strategy",
45 kind: types.Slice,
46 allowedValues: sets.NewString("merge", "retainKeys"),
47 },
48 "listMapKey": extensionAttributes{
49 xName: "x-kubernetes-list-map-keys",
50 kind: types.Slice,
51 },
52 "listType": extensionAttributes{
53 xName: "x-kubernetes-list-type",
54 kind: types.Slice,
55 allowedValues: sets.NewString("atomic", "set", "map"),
56 },
57}
58
59// Extension encapsulates information necessary to generate an OpenAPI extension.
60type extension struct {
61 idlTag string // Example: listType
62 xName string // Example: x-kubernetes-list-type
63 values []string // Example: [atomic]
64}
65
66func (e extension) hasAllowedValues() bool {
67 return tagToExtension[e.idlTag].allowedValues.Len() > 0
68}
69
70func (e extension) allowedValues() sets.String {
71 return tagToExtension[e.idlTag].allowedValues
72}
73
74func (e extension) hasKind() bool {
75 return len(tagToExtension[e.idlTag].kind) > 0
76}
77
78func (e extension) kind() types.Kind {
79 return tagToExtension[e.idlTag].kind
80}
81
82func (e extension) validateAllowedValues() error {
83 // allowedValues not set means no restrictions on values.
84 if !e.hasAllowedValues() {
85 return nil
86 }
87 // Check for missing value.
88 if len(e.values) == 0 {
89 return fmt.Errorf("%s needs a value, none given.", e.idlTag)
90 }
91 // For each extension value, validate that it is allowed.
92 allowedValues := e.allowedValues()
93 if !allowedValues.HasAll(e.values...) {
94 return fmt.Errorf("%v not allowed for %s. Allowed values: %v",
95 e.values, e.idlTag, allowedValues.List())
96 }
97 return nil
98}
99
100func (e extension) validateType(kind types.Kind) error {
101 // If this extension class has no kind, then don't validate the type.
102 if !e.hasKind() {
103 return nil
104 }
105 if kind != e.kind() {
106 return fmt.Errorf("tag %s on type %v; only allowed on type %v",
107 e.idlTag, kind, e.kind())
108 }
109 return nil
110}
111
112func (e extension) hasMultipleValues() bool {
113 return len(e.values) > 1
114}
115
116// Returns sorted list of map keys. Needed for deterministic testing.
117func sortedMapKeys(m map[string][]string) []string {
118 keys := make([]string, len(m))
119 i := 0
120 for k := range m {
121 keys[i] = k
122 i++
123 }
124 sort.Strings(keys)
125 return keys
126}
127
128// Parses comments to return openapi extensions. Returns a list of
129// extensions which parsed correctly, as well as a list of the
130// parse errors. Validating extensions is performed separately.
131// NOTE: Non-empty errors does not mean extensions is empty.
132func parseExtensions(comments []string) ([]extension, []error) {
133 extensions := []extension{}
134 errors := []error{}
135 // First, generate extensions from "+k8s:openapi-gen=x-kubernetes-*" annotations.
136 values := getOpenAPITagValue(comments)
137 for _, val := range values {
138 // Example: x-kubernetes-member-tag:member_test
139 if strings.HasPrefix(val, extensionPrefix) {
140 parts := strings.SplitN(val, ":", 2)
141 if len(parts) != 2 {
142 errors = append(errors, fmt.Errorf("invalid extension value: %v", val))
143 continue
144 }
145 e := extension{
146 idlTag: tagName, // Example: k8s:openapi-gen
147 xName: parts[0], // Example: x-kubernetes-member-tag
148 values: []string{parts[1]}, // Example: member_test
149 }
150 extensions = append(extensions, e)
151 }
152 }
153 // Next, generate extensions from "idlTags" (e.g. +listType)
154 tagValues := types.ExtractCommentTags("+", comments)
155 for _, idlTag := range sortedMapKeys(tagValues) {
156 xAttrs, exists := tagToExtension[idlTag]
157 if !exists {
158 continue
159 }
160 values := tagValues[idlTag]
161 e := extension{
162 idlTag: idlTag, // listType
163 xName: xAttrs.xName, // x-kubernetes-list-type
164 values: values, // [atomic]
165 }
166 extensions = append(extensions, e)
167 }
168 return extensions, errors
169}
170
171func validateMemberExtensions(extensions []extension, m *types.Member) []error {
172 errors := []error{}
173 for _, e := range extensions {
174 if err := e.validateAllowedValues(); err != nil {
175 errors = append(errors, err)
176 }
177 if err := e.validateType(m.Type.Kind); err != nil {
178 errors = append(errors, err)
179 }
180 }
181 return errors
182}