@@ -32,24 +32,30 @@ import (
32
32
// Error strings.
33
33
const (
34
34
errUpdateObject = "cannot update object"
35
+
36
+ // taken from k8s.io/apiserver. Not crucial to match, but for uniformity it
37
+ // better should.
38
+ // TODO(sttts): import from k8s.io/apiserver/pkg/registry/generic/registry when
39
+ // kube has updated otel dependencies post-1.28.
40
+ errOptimisticLock = "the object has been modified; please apply your changes to the latest version and try again"
35
41
)
36
42
37
43
// An APIPatchingApplicator applies changes to an object by either creating or
38
44
// patching it in a Kubernetes API server.
39
45
type APIPatchingApplicator struct {
40
- client client.Client
41
- optionalLog logging. Logger // can be nil
46
+ client client.Client
47
+ log logging. Logger
42
48
}
43
49
44
50
// NewAPIPatchingApplicator returns an Applicator that applies changes to an
45
51
// object by either creating or patching it in a Kubernetes API server.
46
52
func NewAPIPatchingApplicator (c client.Client ) * APIPatchingApplicator {
47
- return & APIPatchingApplicator {client : c }
53
+ return & APIPatchingApplicator {client : c , log : logging . NewNopLogger () }
48
54
}
49
55
50
56
// WithLogger sets the logger on the APIPatchingApplicator.
51
57
func (a * APIPatchingApplicator ) WithLogger (l logging.Logger ) * APIPatchingApplicator {
52
- a .optionalLog = l
58
+ a .log = l
53
59
return a
54
60
}
55
61
@@ -58,43 +64,49 @@ func (a *APIPatchingApplicator) WithLogger(l logging.Logger) *APIPatchingApplica
58
64
// patched if the passed object has the same or an empty resource version.
59
65
func (a * APIPatchingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error { //nolint:gocyclo // the logic here is crucial and deserves to stay in one method
60
66
if obj .GetName () == "" && obj .GetGenerateName () != "" {
61
- return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
67
+ log := a .log .WithValues (logging .ForResource (obj ))
68
+ log .Info ("creating object" )
69
+ return a .client .Create (ctx , obj )
62
70
}
63
71
64
72
current := obj .DeepCopyObject ().(client.Object )
65
73
err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
66
74
if kerrors .IsNotFound (err ) {
67
75
// TODO(negz): Apply ApplyOptions here too?
68
- return errors . Wrap ( a .client .Create (ctx , obj ), "cannot create object" )
76
+ return a .client .Create (ctx , obj )
69
77
}
70
78
if err != nil {
71
- return errors . Wrap ( err , "cannot get object" )
79
+ return err
72
80
}
73
81
74
82
// Note: this check would ideally not be necessary if the Apply signature
75
- // had a current object that we could us for the diff. But we have no
83
+ // had a current object that we could use for the diff. But we have no
76
84
// current and for consistency of the patch it matters that the object we
77
85
// get above is the one that was originally used.
78
86
if obj .GetResourceVersion () != "" && obj .GetResourceVersion () != current .GetResourceVersion () {
79
87
gvr , err := groupResource (a .client , obj )
80
88
if err != nil {
81
89
return err
82
90
}
83
- return kerrors .NewConflict (gvr , current .GetName (), errors .New ("resource version does not match" ))
91
+ return kerrors .NewConflict (gvr , current .GetName (), errors .New (errOptimisticLock ))
84
92
}
85
93
86
94
for _ , fn := range ao {
87
95
if err := fn (ctx , current , obj ); err != nil {
88
- return err
96
+ return errors . Wrapf ( err , "apply option failed for %s" , HumanReadableReference ( a . client , obj ))
89
97
}
90
98
}
91
99
92
- if err := LogDiff (a .optionalLog , current , obj ); err != nil {
93
- return err
100
+ // log diff
101
+ patch := client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})
102
+ patchBytes , err := patch .Data (obj )
103
+ if err != nil {
104
+ return errors .Wrapf (err , "failed to diff %s" , HumanReadableReference (a .client , obj ))
94
105
}
106
+ log := a .log .WithValues (logging .ForResource (obj ))
107
+ log .WithValues ("diff" , string (patchBytes )).Info ("patching object" )
95
108
96
- // TODO(negz): Allow callers to override the kind of patch used.
97
- return errors .Wrap (a .client .Patch (ctx , obj , client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})), "cannot patch object" )
109
+ return a .client .Patch (ctx , obj , client .RawPatch (patch .Type (), patchBytes ))
98
110
}
99
111
100
112
func groupResource (c client.Client , o client.Object ) (schema.GroupResource , error ) {
@@ -112,8 +124,8 @@ func groupResource(c client.Client, o client.Object) (schema.GroupResource, erro
112
124
// An APIUpdatingApplicator applies changes to an object by either creating or
113
125
// updating it in a Kubernetes API server.
114
126
type APIUpdatingApplicator struct {
115
- client client.Client
116
- optionalLog logging. Logger // can be nil
127
+ client client.Client
128
+ log logging. Logger
117
129
}
118
130
119
131
// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
@@ -122,43 +134,50 @@ type APIUpdatingApplicator struct {
122
134
// Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
123
135
// can lead to data-loss if the Golang types in this process are not up-to-date.
124
136
func NewAPIUpdatingApplicator (c client.Client ) * APIUpdatingApplicator {
125
- return & APIUpdatingApplicator {client : c }
137
+ return & APIUpdatingApplicator {client : c , log : logging . NewNopLogger () }
126
138
}
127
139
128
140
// WithLogger sets the logger on the APIUpdatingApplicator.
129
141
func (a * APIUpdatingApplicator ) WithLogger (l logging.Logger ) * APIUpdatingApplicator {
130
- a .optionalLog = l
142
+ a .log = l
131
143
return a
132
144
}
133
145
134
146
// Apply changes to the supplied object. The object will be created if it does
135
147
// not exist, or updated if it does.
136
148
func (a * APIUpdatingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
137
149
if obj .GetName () == "" && obj .GetGenerateName () != "" {
138
- return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
150
+ log := a .log .WithValues (logging .ForResource (obj ))
151
+ log .Info ("creating object" )
152
+ return a .client .Create (ctx , obj )
139
153
}
140
154
141
155
current := obj .DeepCopyObject ().(client.Object )
142
156
err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
143
157
if kerrors .IsNotFound (err ) {
144
158
// TODO(negz): Apply ApplyOptions here too?
145
- return errors . Wrap ( a .client .Create (ctx , obj ), "cannot create object" )
159
+ return a .client .Create (ctx , obj )
146
160
}
147
161
if err != nil {
148
- return errors . Wrap ( err , "cannot get object" )
162
+ return err
149
163
}
150
164
151
165
for _ , fn := range ao {
152
166
if err := fn (ctx , current , obj ); err != nil {
153
- return err
167
+ return errors . Wrapf ( err , "apply option failed for %s" , HumanReadableReference ( a . client , obj ))
154
168
}
155
169
}
156
170
157
- if err := LogDiff (a .optionalLog , current , obj ); err != nil {
158
- return err
171
+ // log diff
172
+ patch := client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})
173
+ patchBytes , err := patch .Data (obj )
174
+ if err != nil {
175
+ return errors .Wrapf (err , "failed to diff %s" , HumanReadableReference (a .client , obj ))
159
176
}
177
+ log := a .log .WithValues (logging .ForResource (obj ))
178
+ log .WithValues ("diff" , string (patchBytes )).Info ("updating object" )
160
179
161
- return errors . Wrap ( a .client .Update (ctx , obj ), "cannot update object" )
180
+ return a .client .Update (ctx , obj )
162
181
}
163
182
164
183
// An APIFinalizer adds and removes finalizers to and from a resource.
0 commit comments