1
16
17 package resource
18
19 import (
20 "context"
21 "fmt"
22
23 apierrors "k8s.io/apimachinery/pkg/api/errors"
24 "k8s.io/apimachinery/pkg/api/meta"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26
27 "k8s.io/apimachinery/pkg/fields"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/apimachinery/pkg/watch"
31 )
32
33 var metadataAccessor = meta.NewAccessor()
34
35
36
37 type Helper struct {
38
39 Resource string
40
41 Subresource string
42
43 RESTClient RESTClient
44
45 NamespaceScoped bool
46
47
48
49
50
51
52 ServerDryRun bool
53
54
55
56 FieldManager string
57
58
59
60 FieldValidation string
61 }
62
63
64 func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
65 return &Helper{
66 Resource: mapping.Resource.Resource,
67 RESTClient: client,
68 NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
69 }
70 }
71
72
73
74 func (m *Helper) DryRun(dryRun bool) *Helper {
75 m.ServerDryRun = dryRun
76 return m
77 }
78
79
80
81 func (m *Helper) WithFieldManager(fieldManager string) *Helper {
82 m.FieldManager = fieldManager
83 return m
84 }
85
86
87
88 func (m *Helper) WithFieldValidation(validationDirective string) *Helper {
89 m.FieldValidation = validationDirective
90 return m
91 }
92
93
94 func (m *Helper) WithSubresource(subresource string) *Helper {
95 m.Subresource = subresource
96 return m
97 }
98
99 func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
100 req := m.RESTClient.Get().
101 NamespaceIfScoped(namespace, m.NamespaceScoped).
102 Resource(m.Resource).
103 Name(name).
104 SubResource(m.Subresource)
105 return req.Do(context.TODO()).Get()
106 }
107
108 func (m *Helper) List(namespace, apiVersion string, options *metav1.ListOptions) (runtime.Object, error) {
109 req := m.RESTClient.Get().
110 NamespaceIfScoped(namespace, m.NamespaceScoped).
111 Resource(m.Resource).
112 VersionedParams(options, metav1.ParameterCodec)
113 return req.Do(context.TODO()).Get()
114 }
115
116
117
118
119 func FollowContinue(initialOpts *metav1.ListOptions,
120 listFunc func(metav1.ListOptions) (runtime.Object, error)) error {
121 opts := initialOpts
122 for {
123 list, err := listFunc(*opts)
124 if err != nil {
125 return err
126 }
127 nextContinueToken, _ := metadataAccessor.Continue(list)
128 if len(nextContinueToken) == 0 {
129 return nil
130 }
131 opts.Continue = nextContinueToken
132 }
133 }
134
135
136
137 func EnhanceListError(err error, opts metav1.ListOptions, subj string) error {
138 if apierrors.IsResourceExpired(err) {
139 return err
140 }
141 if apierrors.IsBadRequest(err) || apierrors.IsNotFound(err) {
142 if se, ok := err.(*apierrors.StatusError); ok {
143
144 if len(opts.LabelSelector) == 0 && len(opts.FieldSelector) == 0 {
145 se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", subj,
146 se.ErrStatus.Message)
147 } else {
148 se.ErrStatus.Message = fmt.Sprintf(
149 "Unable to find %q that match label selector %q, field selector %q: %v", subj,
150 opts.LabelSelector,
151 opts.FieldSelector, se.ErrStatus.Message)
152 }
153 return se
154 }
155 if len(opts.LabelSelector) == 0 && len(opts.FieldSelector) == 0 {
156 return fmt.Errorf("Unable to list %q: %v", subj, err)
157 }
158 return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v",
159 subj, opts.LabelSelector, opts.FieldSelector, err)
160 }
161 return err
162 }
163
164 func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {
165 options.Watch = true
166 return m.RESTClient.Get().
167 NamespaceIfScoped(namespace, m.NamespaceScoped).
168 Resource(m.Resource).
169 VersionedParams(options, metav1.ParameterCodec).
170 Watch(context.TODO())
171 }
172
173 func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
174 return m.RESTClient.Get().
175 NamespaceIfScoped(namespace, m.NamespaceScoped).
176 Resource(m.Resource).
177 VersionedParams(&metav1.ListOptions{
178 ResourceVersion: resourceVersion,
179 Watch: true,
180 FieldSelector: fields.OneTermEqualSelector("metadata.name", name).String(),
181 }, metav1.ParameterCodec).
182 Watch(context.TODO())
183 }
184
185 func (m *Helper) Delete(namespace, name string) (runtime.Object, error) {
186 return m.DeleteWithOptions(namespace, name, nil)
187 }
188
189 func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
190 if options == nil {
191 options = &metav1.DeleteOptions{}
192 }
193 if m.ServerDryRun {
194 options.DryRun = []string{metav1.DryRunAll}
195 }
196
197 return m.RESTClient.Delete().
198 NamespaceIfScoped(namespace, m.NamespaceScoped).
199 Resource(m.Resource).
200 Name(name).
201 Body(options).
202 Do(context.TODO()).
203 Get()
204 }
205
206 func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
207 return m.CreateWithOptions(namespace, modify, obj, nil)
208 }
209
210 func (m *Helper) CreateWithOptions(namespace string, modify bool, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) {
211 if options == nil {
212 options = &metav1.CreateOptions{}
213 }
214 if m.ServerDryRun {
215 options.DryRun = []string{metav1.DryRunAll}
216 }
217 if m.FieldManager != "" {
218 options.FieldManager = m.FieldManager
219 }
220 if m.FieldValidation != "" {
221 options.FieldValidation = m.FieldValidation
222 }
223 if modify {
224
225 version, err := metadataAccessor.ResourceVersion(obj)
226 if err != nil {
227
228 return m.createResource(m.RESTClient, m.Resource, namespace, obj, options)
229 }
230 if version != "" {
231 if err := metadataAccessor.SetResourceVersion(obj, ""); err != nil {
232 return nil, err
233 }
234 }
235 }
236
237 return m.createResource(m.RESTClient, m.Resource, namespace, obj, options)
238 }
239
240 func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) {
241 return c.Post().
242 NamespaceIfScoped(namespace, m.NamespaceScoped).
243 Resource(resource).
244 VersionedParams(options, metav1.ParameterCodec).
245 Body(obj).
246 Do(context.TODO()).
247 Get()
248 }
249 func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte, options *metav1.PatchOptions) (runtime.Object, error) {
250 if options == nil {
251 options = &metav1.PatchOptions{}
252 }
253 if m.ServerDryRun {
254 options.DryRun = []string{metav1.DryRunAll}
255 }
256 if m.FieldManager != "" {
257 options.FieldManager = m.FieldManager
258 }
259 if m.FieldValidation != "" {
260 options.FieldValidation = m.FieldValidation
261 }
262 return m.RESTClient.Patch(pt).
263 NamespaceIfScoped(namespace, m.NamespaceScoped).
264 Resource(m.Resource).
265 Name(name).
266 SubResource(m.Subresource).
267 VersionedParams(options, metav1.ParameterCodec).
268 Body(data).
269 Do(context.TODO()).
270 Get()
271 }
272
273 func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Object) (runtime.Object, error) {
274 c := m.RESTClient
275 var options = &metav1.UpdateOptions{}
276 if m.ServerDryRun {
277 options.DryRun = []string{metav1.DryRunAll}
278 }
279 if m.FieldManager != "" {
280 options.FieldManager = m.FieldManager
281 }
282 if m.FieldValidation != "" {
283 options.FieldValidation = m.FieldValidation
284 }
285
286
287 version, err := metadataAccessor.ResourceVersion(obj)
288 if err != nil {
289
290 return m.replaceResource(c, m.Resource, namespace, name, obj, options)
291 }
292 if version == "" && overwrite {
293
294 serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).SubResource(m.Subresource).Do(context.TODO()).Get()
295 if err != nil {
296
297 return m.replaceResource(c, m.Resource, namespace, name, obj, options)
298 }
299 serverVersion, err := metadataAccessor.ResourceVersion(serverObj)
300 if err != nil {
301 return nil, err
302 }
303 if err := metadataAccessor.SetResourceVersion(obj, serverVersion); err != nil {
304 return nil, err
305 }
306 }
307
308 return m.replaceResource(c, m.Resource, namespace, name, obj, options)
309 }
310
311 func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string, obj runtime.Object, options *metav1.UpdateOptions) (runtime.Object, error) {
312 return c.Put().
313 NamespaceIfScoped(namespace, m.NamespaceScoped).
314 Resource(resource).
315 Name(name).
316 SubResource(m.Subresource).
317 VersionedParams(options, metav1.ParameterCodec).
318 Body(obj).
319 Do(context.TODO()).
320 Get()
321 }
322
View as plain text