@@ -121,11 +121,17 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob
121121 if err != nil {
122122 return err
123123 }
124- obj , err = t .updateObject (gvr , obj , ns , isStatus , deleting , opts .DryRun )
124+ obj , needsCreate , err : = t .updateObject (gvr , gvk , obj , ns , isStatus , deleting , allowsCreateOnUpdate ( gvk ) , opts .DryRun )
125125 if err != nil {
126126 return err
127127 }
128- if obj == nil {
128+
129+ if needsCreate {
130+ opts := metav1.CreateOptions {DryRun : opts .DryRun , FieldManager : opts .FieldManager }
131+ return t .Create (gvr , obj , ns , opts )
132+ }
133+
134+ if obj == nil { // Object was deleted in updateObject
129135 return nil
130136 }
131137
@@ -142,72 +148,91 @@ func (t versionedTracker) Patch(gvr schema.GroupVersionResource, obj runtime.Obj
142148 return err
143149 }
144150
151+ gvk , err := apiutil .GVKForObject (obj , t .scheme )
152+ if err != nil {
153+ return err
154+ }
155+
145156 // We apply patches using a client-go reaction that ends up calling the trackers Patch. As we can't change
146157 // that reaction, we use the callstack to figure out if this originated from the status client.
147158 isStatus := bytes .Contains (debug .Stack (), []byte ("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeSubResourceClient).statusPatch" ))
148159
149- obj , err = t .updateObject (gvr , obj , ns , isStatus , false , patchOptions .DryRun )
160+ obj , needsCreate , err : = t .updateObject (gvr , gvk , obj , ns , isStatus , false , allowsCreateOnUpdate ( gvk ) , patchOptions .DryRun )
150161 if err != nil {
151162 return err
152163 }
153- if obj == nil {
164+ if needsCreate {
165+ opts := metav1.CreateOptions {DryRun : patchOptions .DryRun , FieldManager : patchOptions .FieldManager }
166+ return t .Create (gvr , obj , ns , opts )
167+ }
168+
169+ if obj == nil { // Object was deleted in updateObject
154170 return nil
155171 }
156172
157173 return t .upstream .Patch (gvr , obj , ns , patchOptions )
158174}
159175
160- func (t versionedTracker ) updateObject (gvr schema.GroupVersionResource , obj runtime.Object , ns string , isStatus , deleting bool , dryRun []string ) (runtime.Object , error ) {
176+ // updateObject performs a number of validations and changes before the
177+ // related to object updates, such as checking and updating the resourceVersion.
178+ func (t versionedTracker ) updateObject (
179+ gvr schema.GroupVersionResource ,
180+ gvk schema.GroupVersionKind ,
181+ obj runtime.Object ,
182+ ns string ,
183+ isStatus bool ,
184+ deleting bool ,
185+ allowCreateOnUpdate bool ,
186+ dryRun []string ,
187+ ) (result runtime.Object , needsCreate bool , _ error ) {
161188 accessor , err := meta .Accessor (obj )
162189 if err != nil {
163- return nil , fmt .Errorf ("failed to get accessor for object: %w" , err )
190+ return nil , false , fmt .Errorf ("failed to get accessor for object: %w" , err )
164191 }
165192
166193 if accessor .GetName () == "" {
167- gvk , _ := apiutil .GVKForObject (obj , t .scheme )
168- return nil , apierrors .NewInvalid (
194+ return nil , false , apierrors .NewInvalid (
169195 gvk .GroupKind (),
170196 accessor .GetName (),
171197 field.ErrorList {field .Required (field .NewPath ("metadata.name" ), "name is required" )})
172198 }
173199
174- gvk , err := apiutil .GVKForObject (obj , t .scheme )
175- if err != nil {
176- return nil , err
177- }
178-
179200 oldObject , err := t .Get (gvr , ns , accessor .GetName ())
180201 if err != nil {
181202 // If the resource is not found and the resource allows create on update, issue a
182203 // create instead.
183- if apierrors .IsNotFound (err ) && allowsCreateOnUpdate (gvk ) {
184- return nil , t .Create (gvr , obj , ns )
204+ if apierrors .IsNotFound (err ) && allowCreateOnUpdate {
205+ // Pass this info to the caller rather than create, because in the SSA case it
206+ // must be created by calling Apply in the upstream tracker, not Create.
207+ // This is because SSA seems to use the tuple (fieldManager, operation) to differentiate
208+ // fieldManagers. This is also observable with a real kubernetes apiserver.
209+ return obj , true , nil
185210 }
186- return nil , err
211+ return obj , false , err
187212 }
188213
189214 if t .withStatusSubresource .Has (gvk ) {
190215 if isStatus { // copy everything but status and metadata.ResourceVersion from original object
191216 if err := copyStatusFrom (obj , oldObject ); err != nil {
192- return nil , fmt .Errorf ("failed to copy non-status field for object with status subresouce: %w" , err )
217+ return nil , false , fmt .Errorf ("failed to copy non-status field for object with status subresouce: %w" , err )
193218 }
194219 passedRV := accessor .GetResourceVersion ()
195220 if err := copyFrom (oldObject , obj ); err != nil {
196- return nil , fmt .Errorf ("failed to restore non-status fields: %w" , err )
221+ return nil , false , fmt .Errorf ("failed to restore non-status fields: %w" , err )
197222 }
198223 accessor .SetResourceVersion (passedRV )
199224 } else { // copy status from original object
200225 if err := copyStatusFrom (oldObject , obj ); err != nil {
201- return nil , fmt .Errorf ("failed to copy the status for object with status subresource: %w" , err )
226+ return nil , false , fmt .Errorf ("failed to copy the status for object with status subresource: %w" , err )
202227 }
203228 }
204229 } else if isStatus {
205- return nil , apierrors .NewNotFound (gvr .GroupResource (), accessor .GetName ())
230+ return nil , false , apierrors .NewNotFound (gvr .GroupResource (), accessor .GetName ())
206231 }
207232
208233 oldAccessor , err := meta .Accessor (oldObject )
209234 if err != nil {
210- return nil , err
235+ return nil , false , err
211236 }
212237
213238 // If the new object does not have the resource version set and it allows unconditional update,
@@ -230,28 +255,47 @@ func (t versionedTracker) updateObject(gvr schema.GroupVersionResource, obj runt
230255 }
231256
232257 if accessor .GetResourceVersion () != oldAccessor .GetResourceVersion () {
233- return nil , apierrors .NewConflict (gvr .GroupResource (), accessor .GetName (), errors .New ("object was modified" ))
258+ return nil , false , apierrors .NewConflict (gvr .GroupResource (), accessor .GetName (), errors .New ("object was modified" ))
234259 }
235260 if oldAccessor .GetResourceVersion () == "" {
236261 oldAccessor .SetResourceVersion ("0" )
237262 }
238263 intResourceVersion , err := strconv .ParseUint (oldAccessor .GetResourceVersion (), 10 , 64 )
239264 if err != nil {
240- return nil , fmt .Errorf ("can not convert resourceVersion %q to int: %w" , oldAccessor .GetResourceVersion (), err )
265+ return nil , false , fmt .Errorf ("can not convert resourceVersion %q to int: %w" , oldAccessor .GetResourceVersion (), err )
241266 }
242267 intResourceVersion ++
243268 accessor .SetResourceVersion (strconv .FormatUint (intResourceVersion , 10 ))
244269
245270 if ! deleting && ! deletionTimestampEqual (accessor , oldAccessor ) {
246- return nil , fmt .Errorf ("error: Unable to edit %s: metadata.deletionTimestamp field is immutable" , accessor .GetName ())
271+ return nil , false , fmt .Errorf ("error: Unable to edit %s: metadata.deletionTimestamp field is immutable" , accessor .GetName ())
247272 }
248273
249274 if ! accessor .GetDeletionTimestamp ().IsZero () && len (accessor .GetFinalizers ()) == 0 {
250- return nil , t .Delete (gvr , accessor .GetNamespace (), accessor .GetName (), metav1.DeleteOptions {DryRun : dryRun })
275+ return nil , false , t .Delete (gvr , accessor .GetNamespace (), accessor .GetName (), metav1.DeleteOptions {DryRun : dryRun })
251276 }
252- return convertFromUnstructuredIfNecessary (t .scheme , obj )
277+
278+ obj , err = convertFromUnstructuredIfNecessary (t .scheme , obj )
279+ return obj , false , err
253280}
254281func (t versionedTracker ) Apply (gvr schema.GroupVersionResource , applyConfiguration runtime.Object , ns string , opts ... metav1.PatchOptions ) error {
282+ patchOptions , err := getSingleOrZeroOptions (opts )
283+ if err != nil {
284+ return err
285+ }
286+ gvk , err := apiutil .GVKForObject (applyConfiguration , t .scheme )
287+ if err != nil {
288+ return err
289+ }
290+ applyConfiguration , _ , err = t .updateObject (gvr , gvk , applyConfiguration , ns , false , false , true , patchOptions .DryRun )
291+ if err != nil {
292+ return err
293+ }
294+
295+ if applyConfiguration == nil { // Object was deleted in updateObject
296+ return nil
297+ }
298+
255299 return t .upstream .Apply (gvr , applyConfiguration , ns , opts ... )
256300}
257301func (t versionedTracker ) Delete (gvr schema.GroupVersionResource , ns , name string , opts ... metav1.DeleteOptions ) error {
0 commit comments