/
object.go
446 lines (385 loc) · 10.4 KB
/
object.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
package hz
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/verifa/horizon/pkg/internal/managedfields"
)
// Objecter is an interface that represents an object in the Horizon API.
type Objecter interface {
ObjectKeyer
ObjectRevision() *uint64
ObjectDeletionTimestamp() *Time
ObjectOwnerReferences() []OwnerReference
ObjectOwnerReference(Objecter) (OwnerReference, bool)
ObjectManagedFields() managedfields.ManagedFields
}
// ObjectKeyer is an interface that can produce a unique key for an object.
type ObjectKeyer interface {
ObjectGroup() string
ObjectVersion() string
ObjectKind() string
ObjectAccount() string
ObjectName() string
}
func validateKeyStrict(key ObjectKeyer) error {
var errs error
isEmptyOrStar := func(s string) bool {
return s == "" || s == "*"
}
if isEmptyOrStar(key.ObjectGroup()) {
errs = errors.Join(errs, fmt.Errorf("group is required"))
}
if isEmptyOrStar(key.ObjectVersion()) {
errs = errors.Join(errs, fmt.Errorf("version is required"))
}
if isEmptyOrStar(key.ObjectKind()) {
errs = errors.Join(errs, fmt.Errorf("kind is required"))
}
if isEmptyOrStar(key.ObjectAccount()) {
errs = errors.Join(errs, fmt.Errorf("account is required"))
}
if isEmptyOrStar(key.ObjectName()) {
errs = errors.Join(errs, fmt.Errorf("name is required"))
}
return errs
}
// KeyFromObject takes an ObjectKeyer and returns a string key.
// Any empty fields in the ObjectKeyer are replaced with "*" which works well
// for nats subjects to list objects.
//
// If performing an action on a specific object (e.g. get, create, apply) the
// key cannot contain "*".
// In this case you can use [KeyFromObjectStrict] which makes sure the
// ObjectKeyer is concrete.
func KeyFromObject(obj ObjectKeyer) string {
group := "*"
if obj.ObjectGroup() != "" {
group = obj.ObjectGroup()
}
version := "*"
if obj.ObjectVersion() != "" {
version = obj.ObjectVersion()
}
kind := "*"
if obj.ObjectKind() != "" {
kind = obj.ObjectKind()
}
account := "*"
if obj.ObjectAccount() != "" {
account = obj.ObjectAccount()
}
name := "*"
if obj.ObjectName() != "" {
name = obj.ObjectName()
}
return fmt.Sprintf(
"%s.%s.%s.%s.%s",
group,
version,
kind,
account,
name,
)
}
// KeyFromObjectStrict takes an ObjectKeyer and returns a string key.
// It returns an error if any of the fields are empty (except APIVersion).
// This is useful when you want to ensure the key is concrete when performing
// operations on specific objects (e.g. get, create, apply).
func KeyFromObjectStrict(obj ObjectKeyer) (string, error) {
if err := validateKeyStrict(obj); err != nil {
return "", err
}
return KeyFromObject(obj), nil
}
func ObjectKeyFromString(key string) (ObjectKey, error) {
parts := strings.Split(key, ".")
if len(parts) != 5 {
return ObjectKey{}, fmt.Errorf("invalid key: %q", key)
}
return ObjectKey{
Group: parts[0],
Version: parts[1],
Kind: parts[2],
Account: parts[3],
Name: parts[4],
}, nil
}
func ObjectKeyFromObject(object Objecter) ObjectKey {
return ObjectKey{
Group: object.ObjectGroup(),
Version: object.ObjectVersion(),
Kind: object.ObjectKind(),
Account: object.ObjectAccount(),
Name: object.ObjectName(),
}
}
var _ ObjectKeyer = (*ObjectKey)(nil)
type ObjectKey struct {
Group string
Version string
Kind string
Account string
Name string
}
func (o ObjectKey) ObjectGroup() string {
if o.Group == "" {
return "*"
}
return o.Group
}
func (o ObjectKey) ObjectVersion() string {
if o.Version == "" {
return "*"
}
return o.Version
}
func (o ObjectKey) ObjectKind() string {
if o.Kind == "" {
return "*"
}
return o.Kind
}
func (o ObjectKey) ObjectAccount() string {
if o.Account == "" {
return "*"
}
return o.Account
}
func (o ObjectKey) ObjectName() string {
if o.Name == "" {
return "*"
}
return o.Name
}
func (o ObjectKey) String() string {
return fmt.Sprintf(
"%s.%s.%s.%s.%s",
o.ObjectGroup(),
o.ObjectVersion(),
o.ObjectKind(),
o.ObjectAccount(),
o.ObjectName(),
)
}
type ObjectMeta struct {
Name string `json:"name,omitempty" cue:"=~\"^[a-zA-Z0-9-_]+$\""`
Account string `json:"account,omitempty" cue:"=~\"^[a-zA-Z0-9-_]+$\""`
Labels map[string]string `json:"labels,omitempty" cue:",opt"`
// Revision is the revision number of the object.
Revision *uint64 `json:"revision,omitempty" cue:",opt"`
OwnerReferences OwnerReferences `json:"ownerReferences,omitempty" cue:",opt"`
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" cue:",opt"`
ManagedFields managedfields.ManagedFields `json:"managedFields,omitempty" cue:",opt"`
// Finalizers are a way for controllers to prevent garbage collection of
// objects. The GC will not delete an object unless it has no finalizers.
// Hence, it is the responsibility of the controller to remove the
// finalizers once the object has been marked for deletion (by setting the
// deletionTimestamp).
//
// Use type alias to "correctly" marshal to json.
// A nil Finalizers is omitted from JSON.
// A non-nil Finalizers is marshalled as an empty array if it is empty.
Finalizers *Finalizers `json:"finalizers,omitempty" cue:",opt"`
}
func (o ObjectMeta) ObjectName() string {
return o.Name
}
func (o ObjectMeta) ObjectAccount() string {
return o.Account
}
func (o ObjectMeta) ObjectRevision() *uint64 {
return o.Revision
}
func (o ObjectMeta) ObjectDeletionTimestamp() *Time {
return o.DeletionTimestamp
}
// ObjectDeleteNow returns true if the object has a deletion timestamp that
// has expired, and the controller should therefore delete the object.
func (o ObjectMeta) ObjectDeleteNow() bool {
return o.DeletionTimestamp != nil && o.DeletionTimestamp.Before(time.Now())
}
func (o ObjectMeta) ObjectOwnerReferences() []OwnerReference {
return o.OwnerReferences
}
func (o ObjectMeta) ObjectOwnerReference(
owner Objecter,
) (OwnerReference, bool) {
if o.OwnerReferences == nil {
return OwnerReference{}, false
}
for _, ow := range o.OwnerReferences {
if ow.IsOwnedBy(owner) {
return ow, true
}
}
return OwnerReference{}, false
}
func (o ObjectMeta) ObjectManagedFields() managedfields.ManagedFields {
return o.ManagedFields
}
type TypeMeta struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
}
func (t TypeMeta) ObjectKind() string {
return t.Kind
}
func (t TypeMeta) ObjectGroup() string {
parts := strings.Split(t.APIVersion, "/")
if len(parts) != 2 {
return ""
}
return parts[0]
}
func (t TypeMeta) ObjectVersion() string {
parts := strings.Split(t.APIVersion, "/")
if len(parts) != 2 {
return ""
}
return parts[1]
}
func OwnerReferenceFromObject(object Objecter) OwnerReference {
return OwnerReference{
Group: object.ObjectGroup(),
Version: object.ObjectVersion(),
Kind: object.ObjectKind(),
Name: object.ObjectName(),
Account: object.ObjectAccount(),
}
}
type OwnerReferences []OwnerReference
func (o OwnerReferences) IsOwnedBy(obj Objecter) bool {
for _, owner := range o {
if owner.IsOwnedBy(obj) {
return true
}
}
return false
}
var _ ObjectKeyer = (*OwnerReference)(nil)
type OwnerReference struct {
Group string `json:"group,omitempty" cue:""`
Version string `json:"version,omitempty" cue:""`
Kind string `json:"kind,omitempty" cue:""`
Account string `json:"account,omitempty" cue:""`
Name string `json:"name,omitempty" cue:""`
}
func (o OwnerReference) ObjectGroup() string {
return o.Group
}
func (o OwnerReference) ObjectVersion() string {
return o.Version
}
func (o OwnerReference) ObjectKind() string {
return o.Kind
}
func (o OwnerReference) ObjectAccount() string {
return o.Account
}
func (o OwnerReference) ObjectName() string {
return o.Name
}
func (o OwnerReference) IsOwnedBy(owner Objecter) bool {
if owner == nil {
return false
}
return o.Group == owner.ObjectGroup() &&
o.Version == owner.ObjectVersion() &&
o.Kind == owner.ObjectKind() &&
o.Name == owner.ObjectName() &&
o.Account == owner.ObjectAccount()
}
type Time struct {
time.Time
}
func (t *Time) IsPast() bool {
if t == nil {
return false
}
return t.Before(time.Now())
}
// Finalizers are a way to prevent garbage collection of objects until a
// controller has finished some cleanup logic.
type Finalizers []string
func (f *Finalizers) Contains(finalizer string) bool {
if f == nil {
return false
}
for _, s := range *f {
if s == finalizer {
return true
}
}
return false
}
func (f *Finalizers) String() string {
if f == nil {
return ""
}
return strings.Join(*f, ",")
}
var _ Objecter = (*MetaOnlyObject)(nil)
// MetaOnlyObject is an object that has no spec or status.
// It is used for unmarshalling objects from the store to read metadata.
type MetaOnlyObject struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata"`
}
var _ Objecter = (*GenericObject)(nil)
// GenericObject represents a generic object, containing the type and object
// meta, and also a body.
// GenericObject does not care what the body is, but it stores it so that you
// can unmarshal objects as a GenericObject, perform operations on the metadata
// and marshal back to JSON with the full body.
//
// If you only want to unmarshal and get the type or object meta, use
// [MetaOnlyObject] instead.
type GenericObject struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
Remaining map[string]json.RawMessage `json:"-"`
}
// MarhsalJSON marshals the object to JSON.
func (g GenericObject) MarshalJSON() ([]byte, error) {
objMap := map[string]interface{}{}
for k, v := range g.Remaining {
objMap[k] = v
}
// Marshal the object into JSON and unmarshal it into the object map.
// This might not be the most efficient but feels safer than modifying the
// map manually.
type genAlias GenericObject
obj := genAlias(g)
b, err := json.Marshal(obj)
if err != nil {
return nil, err
}
if err := json.Unmarshal(b, &objMap); err != nil {
return nil, err
}
return json.Marshal(objMap)
}
func (g *GenericObject) UnmarshalJSON(data []byte) error {
type genAlias GenericObject
var obj genAlias
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
if err := json.Unmarshal(data, &obj.Remaining); err != nil {
return err
}
*g = GenericObject(obj)
return nil
}
type GenericObjectList struct {
Items []GenericObject `json:"items,omitempty"`
}
type ObjectList struct {
Items []json.RawMessage `json:"items,omitempty"`
}
type TypedObjectList[T Objecter] struct {
Items []T `json:"items,omitempty"`
}