func (m *VirtualStorage) updateRoleBinding(ctx kapi.Context, name string, objInfo rest.UpdatedObjectInfo, allowEscalation bool) (*authorizationapi.RoleBinding, bool, error) { old, err := m.Get(ctx, name) if err != nil { return nil, false, err } obj, err := objInfo.UpdatedObject(ctx, old) if err != nil { return nil, false, err } roleBinding, ok := obj.(*authorizationapi.RoleBinding) if !ok { return nil, false, kapierrors.NewBadRequest(fmt.Sprintf("obj is not a role: %#v", obj)) } if err := rest.BeforeUpdate(m.UpdateStrategy, ctx, obj, old); err != nil { return nil, false, err } if err := m.validateReferentialIntegrity(ctx, roleBinding); err != nil { return nil, false, err } if !allowEscalation { if err := m.confirmNoEscalation(ctx, roleBinding); err != nil { return nil, false, err } } policyBinding, err := m.getPolicyBindingForPolicy(ctx, roleBinding.RoleRef.Namespace, allowEscalation) if err != nil { return nil, false, err } previousRoleBinding, exists := policyBinding.RoleBindings[roleBinding.Name] if !exists { return nil, false, kapierrors.NewNotFound(authorizationapi.Resource("rolebinding"), roleBinding.Name) } if previousRoleBinding.RoleRef != roleBinding.RoleRef { return nil, false, errors.New("roleBinding.RoleRef may not be modified") } if kapi.Semantic.DeepEqual(previousRoleBinding, roleBinding) { return roleBinding, false, nil } roleBinding.ResourceVersion = policyBinding.ResourceVersion policyBinding.RoleBindings[roleBinding.Name] = roleBinding policyBinding.LastModified = unversioned.Now() if err := m.BindingRegistry.UpdatePolicyBinding(ctx, policyBinding); err != nil { return nil, false, err } return roleBinding, false, nil }
func (m *VirtualStorage) updateRole(ctx kapi.Context, name string, objInfo rest.UpdatedObjectInfo, allowEscalation bool) (*authorizationapi.Role, bool, error) { old, err := m.Get(ctx, name) if err != nil { return nil, false, err } obj, err := objInfo.UpdatedObject(ctx, old) if err != nil { return nil, false, err } role, ok := obj.(*authorizationapi.Role) if !ok { return nil, false, kapierrors.NewBadRequest(fmt.Sprintf("obj is not a role: %#v", obj)) } if err := rest.BeforeUpdate(m.UpdateStrategy, ctx, obj, old); err != nil { return nil, false, err } if !allowEscalation { if err := rulevalidation.ConfirmNoEscalation(ctx, authorizationapi.Resource("role"), role.Name, m.RuleResolver, authorizationinterfaces.NewLocalRoleAdapter(role)); err != nil { return nil, false, err } } policy, err := m.PolicyStorage.GetPolicy(ctx, authorizationapi.PolicyName) if err != nil && kapierrors.IsNotFound(err) { return nil, false, kapierrors.NewNotFound(authorizationapi.Resource("role"), role.Name) } if err != nil { return nil, false, err } oldRole, exists := policy.Roles[role.Name] if !exists { return nil, false, kapierrors.NewNotFound(authorizationapi.Resource("role"), role.Name) } // non-mutating change if kapi.Semantic.DeepEqual(oldRole, role) { return role, false, nil } role.ResourceVersion = policy.ResourceVersion policy.Roles[role.Name] = role policy.LastModified = unversioned.Now() if err := m.PolicyStorage.UpdatePolicy(ctx, policy); err != nil { return nil, false, err } return role, false, nil }
func (r *REST) Update(ctx kapi.Context, obj runtime.Object) (runtime.Object, bool, error) { istag, ok := obj.(*api.ImageStreamTag) if !ok { return nil, false, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamTag: %#v", obj)) } old, err := r.Get(ctx, istag.Name) if err != nil { return nil, false, err } if err := rest.BeforeUpdate(Strategy, ctx, obj, old); err != nil { return nil, false, err } // we only allow updates of annotations, so lets find the correct image stream and update it. name, tag, err := nameAndTag(istag.Name) if err != nil { return nil, false, err } imageStream, err := r.imageStreamRegistry.GetImageStream(ctx, name) if imageStream.Spec.Tags == nil { imageStream.Spec.Tags = map[string]api.TagReference{} } tagRef := imageStream.Spec.Tags[tag] tagRef.Annotations = istag.Annotations imageStream.Spec.Tags[tag] = tagRef newImageStream, err := r.imageStreamRegistry.UpdateImageStream(ctx, imageStream) if err != nil { return nil, false, err } image, err := r.imageFor(ctx, tag, newImageStream) if err != nil { return nil, false, err } newISTag, err := newISTag(tag, newImageStream, image) return newISTag, false, err }
func (m *VirtualStorage) Update(ctx kapi.Context, obj runtime.Object) (runtime.Object, bool, error) { role, ok := obj.(*authorizationapi.Role) if !ok { return nil, false, kapierrors.NewBadRequest(fmt.Sprintf("obj is not a role: %#v", obj)) } old, err := m.Get(ctx, role.Name) if err != nil { return nil, false, err } if err := rest.BeforeUpdate(m.UpdateStrategy, ctx, obj, old); err != nil { return nil, false, err } policy, err := m.PolicyStorage.GetPolicy(ctx, authorizationapi.PolicyName) if err != nil && kapierrors.IsNotFound(err) { return nil, false, kapierrors.NewNotFound("Role", role.Name) } if err != nil { return nil, false, err } if _, exists := policy.Roles[role.Name]; !exists { return nil, false, kapierrors.NewNotFound("Role", role.Name) } role.ResourceVersion = policy.ResourceVersion policy.Roles[role.Name] = role policy.LastModified = util.Now() if err := m.PolicyStorage.UpdatePolicy(ctx, policy); err != nil { return nil, false, err } return role, false, nil }
// Update performs an atomic update and set of the object. Returns the result of the update // or an error. If the registry allows create-on-update, the create flow will be executed. // A bool is returned along with the object and any errors, to indicate object creation. func (e *Store) Update(ctx api.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { key, err := e.KeyFunc(ctx, name) if err != nil { return nil, false, err } var ( creatingObj runtime.Object creating = false ) storagePreconditions := &storage.Preconditions{} if preconditions := objInfo.Preconditions(); preconditions != nil { storagePreconditions.UID = preconditions.UID } out := e.NewFunc() // deleteObj is only used in case a deletion is carried out var deleteObj runtime.Object err = e.Storage.GuaranteedUpdate(ctx, key, out, true, storagePreconditions, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { // Given the existing object, get the new object obj, err := objInfo.UpdatedObject(ctx, existing) if err != nil { return nil, nil, err } // If AllowUnconditionalUpdate() is true and the object specified by the user does not have a resource version, // then we populate it with the latest version. // Else, we check that the version specified by the user matches the version of latest storage object. resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, nil, err } doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() version, err := e.Storage.Versioner().ObjectResourceVersion(existing) if err != nil { return nil, nil, err } if version == 0 { if !e.UpdateStrategy.AllowCreateOnUpdate() { return nil, nil, kubeerr.NewNotFound(e.QualifiedResource, name) } creating = true creatingObj = obj if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { return nil, nil, err } ttl, err := e.calculateTTL(obj, 0, false) if err != nil { return nil, nil, err } return obj, &ttl, nil } creating = false creatingObj = nil if doUnconditionalUpdate { // Update the object's resource version to match the latest storage object's resource version. err = e.Storage.Versioner().UpdateObject(obj, res.ResourceVersion) if err != nil { return nil, nil, err } } else { // Check if the object's resource version matches the latest resource version. newVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, nil, err } if newVersion == 0 { // TODO: The Invalid error should has a field for Resource. // After that field is added, we should fill the Resource and // leave the Kind field empty. See the discussion in #18526. qualifiedKind := unversioned.GroupKind{Group: e.QualifiedResource.Group, Kind: e.QualifiedResource.Resource} fieldErrList := field.ErrorList{field.Invalid(field.NewPath("metadata").Child("resourceVersion"), newVersion, "must be specified for an update")} return nil, nil, kubeerr.NewInvalid(qualifiedKind, name, fieldErrList) } if newVersion != version { return nil, nil, kubeerr.NewConflict(e.QualifiedResource, name, fmt.Errorf(OptimisticLockErrorMsg)) } } if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil { return nil, nil, err } delete := e.shouldDelete(ctx, key, obj, existing) if delete { deleteObj = obj return nil, nil, errEmptiedFinalizers } ttl, err := e.calculateTTL(obj, res.TTL, true) if err != nil { return nil, nil, err } if int64(ttl) != res.TTL { return obj, &ttl, nil } return obj, nil, nil }) if err != nil { // delete the object if err == errEmptiedFinalizers { return e.deleteForEmptyFinalizers(ctx, name, key, deleteObj, storagePreconditions) } if creating { err = storeerr.InterpretCreateError(err, e.QualifiedResource, name) err = rest.CheckGeneratedNameError(e.CreateStrategy, err, creatingObj) } else { err = storeerr.InterpretUpdateError(err, e.QualifiedResource, name) } return nil, false, err } if creating { if e.AfterCreate != nil { if err := e.AfterCreate(out); err != nil { return nil, false, err } } } else { if e.AfterUpdate != nil { if err := e.AfterUpdate(out); err != nil { return nil, false, err } } } if e.Decorator != nil { if err := e.Decorator(out); err != nil { return nil, false, err } } return out, creating, nil }
// Update performs an atomic update and set of the object. Returns the result of the update // or an error. If the registry allows create-on-update, the create flow will be executed. // A bool is returned along with the object and any errors, to indicate object creation. func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { trace := util.NewTrace("Update " + reflect.TypeOf(obj).String()) defer trace.LogIfLong(time.Second) name, err := e.ObjectNameFunc(obj) if err != nil { return nil, false, err } key, err := e.KeyFunc(ctx, name) if err != nil { return nil, false, err } // If AllowUnconditionalUpdate() is true and the object specified by the user does not have a resource version, // then we populate it with the latest version. // Else, we check that the version specified by the user matches the version of latest etcd object. resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, false, err } doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() // TODO: expose TTL creating := false out := e.NewFunc() err = e.Storage.GuaranteedUpdate(ctx, key, out, true, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { version, err := e.Storage.Versioner().ObjectResourceVersion(existing) if err != nil { return nil, nil, err } if version == 0 { if !e.UpdateStrategy.AllowCreateOnUpdate() { return nil, nil, kubeerr.NewNotFound(e.EndpointName, name) } creating = true if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { return nil, nil, err } ttl, err := e.calculateTTL(obj, 0, false) if err != nil { return nil, nil, err } return obj, &ttl, nil } creating = false if doUnconditionalUpdate { // Update the object's resource version to match the latest etcd object's resource version. err = e.Storage.Versioner().UpdateObject(obj, res.Expiration, res.ResourceVersion) if err != nil { return nil, nil, err } } else { // Check if the object's resource version matches the latest resource version. newVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, nil, err } if newVersion != version { return nil, nil, kubeerr.NewConflict(e.EndpointName, name, fmt.Errorf("the object has been modified; please apply your changes to the latest version and try again")) } } if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil { return nil, nil, err } ttl, err := e.calculateTTL(obj, res.TTL, true) if err != nil { return nil, nil, err } if int64(ttl) != res.TTL { return obj, &ttl, nil } return obj, nil, nil }) if err != nil { if creating { err = etcderr.InterpretCreateError(err, e.EndpointName, name) err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj) } else { err = etcderr.InterpretUpdateError(err, e.EndpointName, name) } return nil, false, err } if creating { if e.AfterCreate != nil { if err := e.AfterCreate(out); err != nil { return nil, false, err } } } else { if e.AfterUpdate != nil { if err := e.AfterUpdate(out); err != nil { return nil, false, err } } } if e.Decorator != nil { if err := e.Decorator(obj); err != nil { return nil, false, err } } return out, creating, nil }
func (s *REST) createOrUpdate(ctx kapi.Context, obj runtime.Object, forceCreate bool) (runtime.Object, bool, error) { mapping := obj.(*api.UserIdentityMapping) identity, identityErr, oldUser, oldUserErr, oldMapping, oldMappingErr := s.getRelatedObjects(ctx, mapping.Name) // Ensure we didn't get any errors other than NotFound errors if !(oldMappingErr == nil || kerrs.IsNotFound(oldMappingErr)) { return nil, false, oldMappingErr } if !(identityErr == nil || kerrs.IsNotFound(identityErr)) { return nil, false, identityErr } if !(oldUserErr == nil || kerrs.IsNotFound(oldUserErr)) { return nil, false, oldUserErr } // If we expect to be creating, fail if the mapping already existed if forceCreate && oldMappingErr == nil { return nil, false, kerrs.NewAlreadyExists(api.Resource("useridentitymapping"), oldMapping.Name) } // Allow update to create if missing creating := forceCreate || kerrs.IsNotFound(oldMappingErr) if creating { // Pre-create checks with no access to oldMapping if err := rest.BeforeCreate(Strategy, ctx, mapping); err != nil { return nil, false, err } // Ensure resource version is not specified if len(mapping.ResourceVersion) > 0 { return nil, false, kerrs.NewNotFound(api.Resource("useridentitymapping"), mapping.Name) } } else { // Pre-update checks with access to oldMapping if err := rest.BeforeUpdate(Strategy, ctx, mapping, oldMapping); err != nil { return nil, false, err } // Ensure resource versions match if len(mapping.ResourceVersion) > 0 && mapping.ResourceVersion != oldMapping.ResourceVersion { return nil, false, kerrs.NewConflict(api.Resource("useridentitymapping"), mapping.Name, fmt.Errorf("the resource was updated to %s", oldMapping.ResourceVersion)) } // If we're "updating" to the user we're already pointing to, we're already done if mapping.User.Name == oldMapping.User.Name { return oldMapping, false, nil } } // Validate identity if kerrs.IsNotFound(identityErr) { errs := field.ErrorList{field.Invalid(field.NewPath("identity", "name"), mapping.Identity.Name, "referenced identity does not exist")} return nil, false, kerrs.NewInvalid(api.Kind("UserIdentityMapping"), mapping.Name, errs) } // Get new user newUser, err := s.userRegistry.GetUser(ctx, mapping.User.Name) if kerrs.IsNotFound(err) { errs := field.ErrorList{field.Invalid(field.NewPath("user", "name"), mapping.User.Name, "referenced user does not exist")} return nil, false, kerrs.NewInvalid(api.Kind("UserIdentityMapping"), mapping.Name, errs) } if err != nil { return nil, false, err } // Update the new user to point at the identity. If this fails, Update is re-entrant if addIdentityToUser(identity, newUser) { if _, err := s.userRegistry.UpdateUser(ctx, newUser); err != nil { return nil, false, err } } // Update the identity to point at the new user. If this fails. Update is re-entrant if setIdentityUser(identity, newUser) { if updatedIdentity, err := s.identityRegistry.UpdateIdentity(ctx, identity); err != nil { return nil, false, err } else { identity = updatedIdentity } } // At this point, the mapping for the identity has been updated to the new user // Everything past this point is cleanup // Update the old user to no longer point at the identity. // If this fails, log the error, but continue, because Update is no longer re-entrant if oldUser != nil && removeIdentityFromUser(identity, oldUser) { if _, err := s.userRegistry.UpdateUser(ctx, oldUser); err != nil { utilruntime.HandleError(fmt.Errorf("error removing identity reference %s from user %s: %v", identity.Name, oldUser.Name, err)) } } updatedMapping, err := mappingFor(newUser, identity) return updatedMapping, creating, err }
// TODO: This should be done on types that are not part of our API func TestBeforeUpdate(t *testing.T) { testCases := []struct { name string tweakSvc func(oldSvc, newSvc *api.Service) // given basic valid services, each test case can customize them expectErr bool }{ { name: "no change", tweakSvc: func(oldSvc, newSvc *api.Service) { // nothing }, expectErr: false, }, { name: "change port", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Spec.Ports[0].Port++ }, expectErr: false, }, { name: "bad namespace", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Namespace = "#$%%invalid" }, expectErr: true, }, { name: "change name", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Name += "2" }, expectErr: true, }, { name: "change ClusterIP", tweakSvc: func(oldSvc, newSvc *api.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" newSvc.Spec.ClusterIP = "4.3.2.1" }, expectErr: true, }, { name: "change selectpor", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Spec.Selector = map[string]string{"newkey": "newvalue"} }, expectErr: false, }, } for _, tc := range testCases { oldSvc := makeValidService() newSvc := makeValidService() tc.tweakSvc(&oldSvc, &newSvc) ctx := api.NewDefaultContext() err := rest.BeforeUpdate(Strategy, ctx, runtime.Object(&oldSvc), runtime.Object(&newSvc)) if tc.expectErr && err == nil { t.Errorf("unexpected non-error for %q", tc.name) } if !tc.expectErr && err != nil { t.Errorf("unexpected error for %q: %v", tc.name, err) } } }
func (r *REST) Update(ctx kapi.Context, obj runtime.Object) (runtime.Object, bool, error) { istag, ok := obj.(*api.ImageStreamTag) if !ok { return nil, false, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamTag: %#v", obj)) } name, tag, err := nameAndTag(istag.Name) if err != nil { return nil, false, err } imageStream, err := r.imageStreamRegistry.GetImageStream(ctx, name) if err != nil { if !kapierrors.IsNotFound(err) || len(istag.ResourceVersion) != 0 { return nil, false, err } namespace, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, false, kapierrors.NewBadRequest("namespace is required on ImageStreamTags") } imageStream = &api.ImageStream{ ObjectMeta: kapi.ObjectMeta{Namespace: namespace, Name: name}, } } // check for conflict if len(istag.ResourceVersion) == 0 { istag.ResourceVersion = imageStream.ResourceVersion } if imageStream.ResourceVersion != istag.ResourceVersion { return nil, false, kapierrors.NewConflict(api.Resource("imagestreamtags"), istag.Name, fmt.Errorf("another caller has updated the resource version to %s", imageStream.ResourceVersion)) } // create the synthetic old istag old, err := newISTag(tag, imageStream, nil, true) if err != nil { return nil, false, err } if len(istag.ResourceVersion) == 0 { if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, false, err } } else { if err := rest.BeforeUpdate(Strategy, ctx, obj, old); err != nil { return nil, false, err } } // update the spec tag if imageStream.Spec.Tags == nil { imageStream.Spec.Tags = map[string]api.TagReference{} } tagRef, exists := imageStream.Spec.Tags[tag] // if the caller set tag, override the spec tag if istag.Tag != nil { tagRef = *istag.Tag tagRef.Name = tag } tagRef.Annotations = istag.Annotations imageStream.Spec.Tags[tag] = tagRef // mutate the image stream var newImageStream *api.ImageStream if imageStream.CreationTimestamp.IsZero() { newImageStream, err = r.imageStreamRegistry.CreateImageStream(ctx, imageStream) } else { newImageStream, err = r.imageStreamRegistry.UpdateImageStream(ctx, imageStream) } if err != nil { return nil, false, err } image, err := r.imageFor(ctx, tag, newImageStream) if err != nil { if !kapierrors.IsNotFound(err) { return nil, false, err } } newISTag, err := newISTag(tag, newImageStream, image, true) return newISTag, !exists, err }
func (m *VirtualStorage) updateRole(ctx kapi.Context, name string, objInfo rest.UpdatedObjectInfo, allowEscalation bool) (*authorizationapi.Role, bool, error) { var updatedRole *authorizationapi.Role var roleConflicted = false // Retry if the policy update hits a conflict if err := kclient.RetryOnConflict(kclient.DefaultRetry, func() error { policy, err := m.PolicyStorage.GetPolicy(ctx, authorizationapi.PolicyName) if kapierrors.IsNotFound(err) { return kapierrors.NewNotFound(m.Resource, name) } if err != nil { return err } oldRole, exists := policy.Roles[name] if !exists { return kapierrors.NewNotFound(m.Resource, name) } obj, err := objInfo.UpdatedObject(ctx, oldRole) if err != nil { return err } role, ok := obj.(*authorizationapi.Role) if !ok { return kapierrors.NewBadRequest(fmt.Sprintf("obj is not a role: %#v", obj)) } if len(role.ResourceVersion) == 0 && m.UpdateStrategy.AllowUnconditionalUpdate() { role.ResourceVersion = oldRole.ResourceVersion } if err := rest.BeforeUpdate(m.UpdateStrategy, ctx, obj, oldRole); err != nil { return err } if !allowEscalation { if err := rulevalidation.ConfirmNoEscalation(ctx, m.Resource, role.Name, m.RuleResolver, m.CachedRuleResolver, authorizationinterfaces.NewLocalRoleAdapter(role)); err != nil { return err } } // conflict detection if role.ResourceVersion != oldRole.ResourceVersion { // mark as a conflict err, but return an untyped error to escape the retry roleConflicted = true return errors.New(registry.OptimisticLockErrorMsg) } // non-mutating change if kapi.Semantic.DeepEqual(oldRole, role) { updatedRole = role return nil } role.ResourceVersion = policy.ResourceVersion policy.Roles[role.Name] = role policy.LastModified = unversioned.Now() if err := m.PolicyStorage.UpdatePolicy(ctx, policy); err != nil { return err } updatedRole = role return nil }); err != nil { if roleConflicted { // construct the typed conflict error return nil, false, kapierrors.NewConflict(authorizationapi.Resource("name"), name, err) } return nil, false, err } return updatedRole, false, nil }
func (m *VirtualStorage) updateRoleBinding(ctx kapi.Context, name string, objInfo rest.UpdatedObjectInfo, allowEscalation bool) (*authorizationapi.RoleBinding, bool, error) { var updatedRoleBinding *authorizationapi.RoleBinding var roleBindingConflicted = false if err := kclient.RetryOnConflict(kclient.DefaultRetry, func() error { // Do an initial fetch old, err := m.Get(ctx, name) if err != nil { return err } oldRoleBinding, exists := old.(*authorizationapi.RoleBinding) if !exists { return kapierrors.NewBadRequest(fmt.Sprintf("old obj is not a role binding: %#v", old)) } // get the updated object, so we know what namespace we're binding against obj, err := objInfo.UpdatedObject(ctx, old) if err != nil { return err } roleBinding, ok := obj.(*authorizationapi.RoleBinding) if !ok { return kapierrors.NewBadRequest(fmt.Sprintf("obj is not a role binding: %#v", obj)) } // now that we know which roleRef we want to go to, fetch the policyBinding we'll actually be updating, and re-get the oldRoleBinding policyBinding, err := m.getPolicyBindingForPolicy(ctx, roleBinding.RoleRef.Namespace, allowEscalation) if err != nil { return err } oldRoleBinding, exists = policyBinding.RoleBindings[roleBinding.Name] if !exists { return kapierrors.NewNotFound(authorizationapi.Resource("rolebinding"), roleBinding.Name) } if len(roleBinding.ResourceVersion) == 0 && m.UpdateStrategy.AllowUnconditionalUpdate() { roleBinding.ResourceVersion = oldRoleBinding.ResourceVersion } if err := rest.BeforeUpdate(m.UpdateStrategy, ctx, obj, oldRoleBinding); err != nil { return err } if !allowEscalation { if err := m.confirmNoEscalation(ctx, roleBinding); err != nil { return err } } // conflict detection if roleBinding.ResourceVersion != oldRoleBinding.ResourceVersion { // mark as a conflict err, but return an untyped error to escape the retry roleBindingConflicted = true return errors.New(registry.OptimisticLockErrorMsg) } // non-mutating change if kapi.Semantic.DeepEqual(oldRoleBinding, roleBinding) { updatedRoleBinding = roleBinding return nil } roleBinding.ResourceVersion = policyBinding.ResourceVersion policyBinding.RoleBindings[roleBinding.Name] = roleBinding policyBinding.LastModified = unversioned.Now() if err := m.BindingRegistry.UpdatePolicyBinding(ctx, policyBinding); err != nil { return err } updatedRoleBinding = roleBinding return nil }); err != nil { if roleBindingConflicted { // construct the typed conflict error return nil, false, kapierrors.NewConflict(authorizationapi.Resource("rolebinding"), name, err) } return nil, false, err } return updatedRoleBinding, false, nil }
// Update performs an atomic update and set of the object. Returns the result of the update // or an error. If the registry allows create-on-update, the create flow will be executed. // A bool is returned along with the object and any errors, to indicate object creation. func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { name, err := e.ObjectNameFunc(obj) if err != nil { return nil, false, err } key, err := e.KeyFunc(ctx, name) if err != nil { return nil, false, err } // If AllowUnconditionalUpdate() is true and the object specified by the user does not have a resource version, // then we populate it with the latest version. // Else, we check that the version specified by the user matches the version of latest etcd object. resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, false, err } doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() // TODO: expose TTL creating := false out := e.NewFunc() err = e.Storage.GuaranteedUpdate(ctx, key, out, true, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { // Since we return 'obj' from this function and it can be modified outside this // function, we are resetting resourceVersion to the initial value here. // // TODO: In fact, we should probably return a DeepCopy of obj in all places. err := e.Storage.Versioner().UpdateObject(obj, nil, resourceVersion) if err != nil { return nil, nil, err } version, err := e.Storage.Versioner().ObjectResourceVersion(existing) if err != nil { return nil, nil, err } if version == 0 { if !e.UpdateStrategy.AllowCreateOnUpdate() { return nil, nil, kubeerr.NewNotFound(e.QualifiedResource, name) } creating = true if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { return nil, nil, err } ttl, err := e.calculateTTL(obj, 0, false) if err != nil { return nil, nil, err } return obj, &ttl, nil } creating = false if doUnconditionalUpdate { // Update the object's resource version to match the latest etcd object's resource version. err = e.Storage.Versioner().UpdateObject(obj, res.Expiration, res.ResourceVersion) if err != nil { return nil, nil, err } } else { // Check if the object's resource version matches the latest resource version. newVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) if err != nil { return nil, nil, err } if newVersion == 0 { // TODO: The Invalid error should has a field for Resource. // After that field is added, we should fill the Resource and // leave the Kind field empty. See the discussion in #18526. qualifiedKind := unversioned.GroupKind{e.QualifiedResource.Group, e.QualifiedResource.Resource} fieldErrList := field.ErrorList{field.Invalid(field.NewPath("metadata").Child("resourceVersion"), newVersion, "must be specified for an update")} return nil, nil, kubeerr.NewInvalid(qualifiedKind, name, fieldErrList) } if newVersion != version { return nil, nil, kubeerr.NewConflict(e.QualifiedResource, name, fmt.Errorf("the object has been modified; please apply your changes to the latest version and try again")) } } if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil { return nil, nil, err } ttl, err := e.calculateTTL(obj, res.TTL, true) if err != nil { return nil, nil, err } if int64(ttl) != res.TTL { return obj, &ttl, nil } return obj, nil, nil }) if err != nil { if creating { err = etcderr.InterpretCreateError(err, e.QualifiedResource, name) err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj) } else { err = etcderr.InterpretUpdateError(err, e.QualifiedResource, name) } return nil, false, err } if creating { if e.AfterCreate != nil { if err := e.AfterCreate(out); err != nil { return nil, false, err } } } else { if e.AfterUpdate != nil { if err := e.AfterUpdate(out); err != nil { return nil, false, err } } } if e.Decorator != nil { if err := e.Decorator(obj); err != nil { return nil, false, err } } return out, creating, nil }
func (r *REST) Update(ctx kapi.Context, tagName string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { name, tag, err := nameAndTag(tagName) if err != nil { return nil, false, err } create := false imageStream, err := r.imageStreamRegistry.GetImageStream(ctx, name) if err != nil { if !kapierrors.IsNotFound(err) { return nil, false, err } namespace, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, false, kapierrors.NewBadRequest("namespace is required on ImageStreamTags") } imageStream = &api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: namespace, Name: name, }, } kapi.FillObjectMetaSystemFields(ctx, &imageStream.ObjectMeta) create = true } // create the synthetic old istag old, err := newISTag(tag, imageStream, nil, true) if err != nil { return nil, false, err } obj, err := objInfo.UpdatedObject(ctx, old) if err != nil { return nil, false, err } istag, ok := obj.(*api.ImageStreamTag) if !ok { return nil, false, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamTag: %#v", obj)) } // check for conflict switch { case len(istag.ResourceVersion) == 0: // should disallow blind PUT, but this was previously supported istag.ResourceVersion = imageStream.ResourceVersion case len(imageStream.ResourceVersion) == 0: // image stream did not exist, cannot update return nil, false, kapierrors.NewNotFound(api.Resource("imagestreamtags"), tagName) case imageStream.ResourceVersion != istag.ResourceVersion: // conflicting input and output return nil, false, kapierrors.NewConflict(api.Resource("imagestreamtags"), istag.Name, fmt.Errorf("another caller has updated the resource version to %s", imageStream.ResourceVersion)) } if create { if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, false, err } } else { if err := rest.BeforeUpdate(Strategy, ctx, obj, old); err != nil { return nil, false, err } } // update the spec tag if imageStream.Spec.Tags == nil { imageStream.Spec.Tags = map[string]api.TagReference{} } tagRef, exists := imageStream.Spec.Tags[tag] // if the caller set tag, override the spec tag if istag.Tag != nil { tagRef = *istag.Tag tagRef.Name = tag } tagRef.Annotations = istag.Annotations imageStream.Spec.Tags[tag] = tagRef // mutate the image stream var newImageStream *api.ImageStream if create { newImageStream, err = r.imageStreamRegistry.CreateImageStream(ctx, imageStream) } else { newImageStream, err = r.imageStreamRegistry.UpdateImageStream(ctx, imageStream) } if err != nil { return nil, false, err } image, err := r.imageFor(ctx, tag, newImageStream) if err != nil { if !kapierrors.IsNotFound(err) { return nil, false, err } } newISTag, err := newISTag(tag, newImageStream, image, true) return newISTag, !exists, err }