1
16
17 package apihelpers
18
19 import (
20 "fmt"
21 "net/url"
22 "strings"
23 "time"
24
25 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 )
28
29
30
31 func IsProtectedCommunityGroup(group string) bool {
32 switch {
33 case group == "k8s.io" || strings.HasSuffix(group, ".k8s.io"):
34 return true
35 case group == "kubernetes.io" || strings.HasSuffix(group, ".kubernetes.io"):
36 return true
37 default:
38 return false
39 }
40
41 }
42
43
44 type APIApprovalState int
45
46 const (
47
48 APIApprovalInvalid APIApprovalState = iota
49
50 APIApproved
51
52 APIApprovalBypassed
53
54 APIApprovalMissing
55 )
56
57
58 func GetAPIApprovalState(annotations map[string]string) (state APIApprovalState, reason string) {
59 annotation := annotations[apiextensionsv1.KubeAPIApprovedAnnotation]
60
61
62 url, annotationURLParseErr := url.ParseRequestURI(annotation)
63 switch {
64 case len(annotation) == 0:
65 return APIApprovalMissing, fmt.Sprintf("protected groups must have approval annotation %q, see https://github.com/kubernetes/enhancements/pull/1111", apiextensionsv1.KubeAPIApprovedAnnotation)
66 case strings.HasPrefix(annotation, "unapproved"):
67 return APIApprovalBypassed, fmt.Sprintf("not approved: %q", annotation)
68 case annotationURLParseErr == nil && url != nil && len(url.Host) > 0 && len(url.Scheme) > 0:
69 return APIApproved, fmt.Sprintf("approved in %v", annotation)
70 default:
71 return APIApprovalInvalid, fmt.Sprintf("protected groups must have approval annotation %q with either a URL or a reason starting with \"unapproved\", see https://github.com/kubernetes/enhancements/pull/1111", apiextensionsv1.KubeAPIApprovedAnnotation)
72 }
73 }
74
75
76 func SetCRDCondition(crd *apiextensionsv1.CustomResourceDefinition, newCondition apiextensionsv1.CustomResourceDefinitionCondition) {
77 newCondition.LastTransitionTime = metav1.NewTime(time.Now())
78
79 existingCondition := FindCRDCondition(crd, newCondition.Type)
80 if existingCondition == nil {
81 crd.Status.Conditions = append(crd.Status.Conditions, newCondition)
82 return
83 }
84
85 if existingCondition.Status != newCondition.Status || existingCondition.LastTransitionTime.IsZero() {
86 existingCondition.LastTransitionTime = newCondition.LastTransitionTime
87 }
88
89 existingCondition.Status = newCondition.Status
90 existingCondition.Reason = newCondition.Reason
91 existingCondition.Message = newCondition.Message
92 }
93
94
95 func RemoveCRDCondition(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) {
96 newConditions := []apiextensionsv1.CustomResourceDefinitionCondition{}
97 for _, condition := range crd.Status.Conditions {
98 if condition.Type != conditionType {
99 newConditions = append(newConditions, condition)
100 }
101 }
102 crd.Status.Conditions = newConditions
103 }
104
105
106 func FindCRDCondition(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) *apiextensionsv1.CustomResourceDefinitionCondition {
107 for i := range crd.Status.Conditions {
108 if crd.Status.Conditions[i].Type == conditionType {
109 return &crd.Status.Conditions[i]
110 }
111 }
112
113 return nil
114 }
115
116
117 func IsCRDConditionTrue(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) bool {
118 return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensionsv1.ConditionTrue)
119 }
120
121
122 func IsCRDConditionFalse(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) bool {
123 return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensionsv1.ConditionFalse)
124 }
125
126
127 func IsCRDConditionPresentAndEqual(crd *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType, status apiextensionsv1.ConditionStatus) bool {
128 for _, condition := range crd.Status.Conditions {
129 if condition.Type == conditionType {
130 return condition.Status == status
131 }
132 }
133 return false
134 }
135
136
137 func IsCRDConditionEquivalent(lhs, rhs *apiextensionsv1.CustomResourceDefinitionCondition) bool {
138 if lhs == nil && rhs == nil {
139 return true
140 }
141 if lhs == nil || rhs == nil {
142 return false
143 }
144
145 return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type
146 }
147
148
149 func CRDHasFinalizer(crd *apiextensionsv1.CustomResourceDefinition, needle string) bool {
150 for _, finalizer := range crd.Finalizers {
151 if finalizer == needle {
152 return true
153 }
154 }
155
156 return false
157 }
158
159
160 func CRDRemoveFinalizer(crd *apiextensionsv1.CustomResourceDefinition, needle string) {
161 newFinalizers := []string{}
162 for _, finalizer := range crd.Finalizers {
163 if finalizer != needle {
164 newFinalizers = append(newFinalizers, finalizer)
165 }
166 }
167 crd.Finalizers = newFinalizers
168 }
169
170
171 func HasServedCRDVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) bool {
172 for _, v := range crd.Spec.Versions {
173 if v.Name == version {
174 return v.Served
175 }
176 }
177 return false
178 }
179
180
181 func GetCRDStorageVersion(crd *apiextensionsv1.CustomResourceDefinition) (string, error) {
182 for _, v := range crd.Spec.Versions {
183 if v.Storage {
184 return v.Name, nil
185 }
186 }
187
188 return "", fmt.Errorf("invalid apiextensionsv1.CustomResourceDefinition, no storage version")
189 }
190
191
192 func IsStoredVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) bool {
193 for _, v := range crd.Status.StoredVersions {
194 if version == v {
195 return true
196 }
197 }
198 return false
199 }
200
201
202 func GetSchemaForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) (*apiextensionsv1.CustomResourceValidation, error) {
203 for _, v := range crd.Spec.Versions {
204 if version == v.Name {
205 return v.Schema, nil
206 }
207 }
208 return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name)
209 }
210
211
212 func GetSubresourcesForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) (*apiextensionsv1.CustomResourceSubresources, error) {
213 for _, v := range crd.Spec.Versions {
214 if version == v.Name {
215 return v.Subresources, nil
216 }
217 }
218 return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name)
219 }
220
221
222 func HasPerVersionSchema(versions []apiextensionsv1.CustomResourceDefinitionVersion) bool {
223 for _, v := range versions {
224 if v.Schema != nil {
225 return true
226 }
227 }
228 return false
229 }
230
231
232 func HasPerVersionSubresources(versions []apiextensionsv1.CustomResourceDefinitionVersion) bool {
233 for _, v := range versions {
234 if v.Subresources != nil {
235 return true
236 }
237 }
238 return false
239 }
240
241
242 func HasPerVersionColumns(versions []apiextensionsv1.CustomResourceDefinitionVersion) bool {
243 for _, v := range versions {
244 if len(v.AdditionalPrinterColumns) > 0 {
245 return true
246 }
247 }
248 return false
249 }
250
251
252 func HasVersionServed(crd *apiextensionsv1.CustomResourceDefinition, version string) bool {
253 for _, v := range crd.Spec.Versions {
254 if !v.Served || v.Name != version {
255 continue
256 }
257 return true
258 }
259 return false
260 }
261
View as plain text