reconcile_rolebindings.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package reconciliation
  14. import (
  15. "fmt"
  16. "reflect"
  17. rbacv1 "k8s.io/api/rbac/v1"
  18. "k8s.io/apimachinery/pkg/api/errors"
  19. "k8s.io/apimachinery/pkg/runtime"
  20. "k8s.io/apimachinery/pkg/types"
  21. )
  22. type RoleBindingModifier interface {
  23. Get(namespace, name string) (RoleBinding, error)
  24. Delete(namespace, name string, uid types.UID) error
  25. Create(RoleBinding) (RoleBinding, error)
  26. Update(RoleBinding) (RoleBinding, error)
  27. }
  28. type RoleBinding interface {
  29. GetObject() runtime.Object
  30. GetNamespace() string
  31. GetName() string
  32. GetUID() types.UID
  33. GetLabels() map[string]string
  34. SetLabels(map[string]string)
  35. GetAnnotations() map[string]string
  36. SetAnnotations(map[string]string)
  37. GetRoleRef() rbacv1.RoleRef
  38. GetSubjects() []rbacv1.Subject
  39. SetSubjects([]rbacv1.Subject)
  40. DeepCopyRoleBinding() RoleBinding
  41. }
  42. // ReconcileRoleBindingOptions holds options for running a role binding reconciliation
  43. type ReconcileRoleBindingOptions struct {
  44. // RoleBinding is the expected rolebinding that will be reconciled
  45. RoleBinding RoleBinding
  46. // Confirm indicates writes should be performed. When false, results are returned as a dry-run.
  47. Confirm bool
  48. // RemoveExtraSubjects indicates reconciliation should remove extra subjects from an existing role binding
  49. RemoveExtraSubjects bool
  50. // Client is used to look up existing rolebindings, and create/update the rolebinding when Confirm=true
  51. Client RoleBindingModifier
  52. }
  53. // ReconcileClusterRoleBindingResult holds the result of a reconciliation operation.
  54. type ReconcileClusterRoleBindingResult struct {
  55. // RoleBinding is the reconciled rolebinding from the reconciliation operation.
  56. // If the reconcile was performed as a dry-run, or the existing rolebinding was protected, the reconciled rolebinding is not persisted.
  57. RoleBinding RoleBinding
  58. // MissingSubjects contains expected subjects that were missing from the currently persisted rolebinding
  59. MissingSubjects []rbacv1.Subject
  60. // ExtraSubjects contains extra subjects the currently persisted rolebinding had
  61. ExtraSubjects []rbacv1.Subject
  62. // Operation is the API operation required to reconcile.
  63. // If no reconciliation was needed, it is set to ReconcileNone.
  64. // If options.Confirm == false, the reconcile was in dry-run mode, so the operation was not performed.
  65. // If result.Protected == true, the rolebinding opted out of reconciliation, so the operation was not performed.
  66. // Otherwise, the operation was performed.
  67. Operation ReconcileOperation
  68. // Protected indicates an existing role prevented reconciliation
  69. Protected bool
  70. }
  71. func (o *ReconcileRoleBindingOptions) Run() (*ReconcileClusterRoleBindingResult, error) {
  72. return o.run(0)
  73. }
  74. func (o *ReconcileRoleBindingOptions) run(attempts int) (*ReconcileClusterRoleBindingResult, error) {
  75. // This keeps us from retrying forever if a rolebinding keeps appearing and disappearing as we reconcile.
  76. // Conflict errors on update are handled at a higher level.
  77. if attempts > 3 {
  78. return nil, fmt.Errorf("exceeded maximum attempts")
  79. }
  80. var result *ReconcileClusterRoleBindingResult
  81. existingBinding, err := o.Client.Get(o.RoleBinding.GetNamespace(), o.RoleBinding.GetName())
  82. switch {
  83. case errors.IsNotFound(err):
  84. result = &ReconcileClusterRoleBindingResult{
  85. RoleBinding: o.RoleBinding,
  86. MissingSubjects: o.RoleBinding.GetSubjects(),
  87. Operation: ReconcileCreate,
  88. }
  89. case err != nil:
  90. return nil, err
  91. default:
  92. result, err = computeReconciledRoleBinding(existingBinding, o.RoleBinding, o.RemoveExtraSubjects)
  93. if err != nil {
  94. return nil, err
  95. }
  96. }
  97. // If reconcile-protected, short-circuit
  98. if result.Protected {
  99. return result, nil
  100. }
  101. // If we're in dry-run mode, short-circuit
  102. if !o.Confirm {
  103. return result, nil
  104. }
  105. switch result.Operation {
  106. case ReconcileRecreate:
  107. // Try deleting
  108. err := o.Client.Delete(existingBinding.GetNamespace(), existingBinding.GetName(), existingBinding.GetUID())
  109. switch {
  110. case err == nil, errors.IsNotFound(err):
  111. // object no longer exists, as desired
  112. case errors.IsConflict(err):
  113. // delete failed because our UID precondition conflicted
  114. // this could mean another object exists with a different UID, re-run
  115. return o.run(attempts + 1)
  116. default:
  117. // return other errors
  118. return nil, err
  119. }
  120. // continue to create
  121. fallthrough
  122. case ReconcileCreate:
  123. created, err := o.Client.Create(result.RoleBinding)
  124. // If created since we started this reconcile, re-run
  125. if errors.IsAlreadyExists(err) {
  126. return o.run(attempts + 1)
  127. }
  128. if err != nil {
  129. return nil, err
  130. }
  131. result.RoleBinding = created
  132. case ReconcileUpdate:
  133. updated, err := o.Client.Update(result.RoleBinding)
  134. // If deleted since we started this reconcile, re-run
  135. if errors.IsNotFound(err) {
  136. return o.run(attempts + 1)
  137. }
  138. if err != nil {
  139. return nil, err
  140. }
  141. result.RoleBinding = updated
  142. case ReconcileNone:
  143. // no-op
  144. default:
  145. return nil, fmt.Errorf("invalid operation: %v", result.Operation)
  146. }
  147. return result, nil
  148. }
  149. // computeReconciledRoleBinding returns the rolebinding that must be created and/or updated to make the
  150. // existing rolebinding's subjects, roleref, labels, and annotations match the expected rolebinding
  151. func computeReconciledRoleBinding(existing, expected RoleBinding, removeExtraSubjects bool) (*ReconcileClusterRoleBindingResult, error) {
  152. result := &ReconcileClusterRoleBindingResult{Operation: ReconcileNone}
  153. result.Protected = (existing.GetAnnotations()[rbacv1.AutoUpdateAnnotationKey] == "false")
  154. // Reset the binding completely if the roleRef is different
  155. if expected.GetRoleRef() != existing.GetRoleRef() {
  156. result.RoleBinding = expected
  157. result.Operation = ReconcileRecreate
  158. return result, nil
  159. }
  160. // Start with a copy of the existing object
  161. result.RoleBinding = existing.DeepCopyRoleBinding()
  162. // Merge expected annotations and labels
  163. result.RoleBinding.SetAnnotations(merge(expected.GetAnnotations(), result.RoleBinding.GetAnnotations()))
  164. if !reflect.DeepEqual(result.RoleBinding.GetAnnotations(), existing.GetAnnotations()) {
  165. result.Operation = ReconcileUpdate
  166. }
  167. result.RoleBinding.SetLabels(merge(expected.GetLabels(), result.RoleBinding.GetLabels()))
  168. if !reflect.DeepEqual(result.RoleBinding.GetLabels(), existing.GetLabels()) {
  169. result.Operation = ReconcileUpdate
  170. }
  171. // Compute extra and missing subjects
  172. result.MissingSubjects, result.ExtraSubjects = diffSubjectLists(expected.GetSubjects(), existing.GetSubjects())
  173. switch {
  174. case !removeExtraSubjects && len(result.MissingSubjects) > 0:
  175. // add missing subjects in the union case
  176. result.RoleBinding.SetSubjects(append(result.RoleBinding.GetSubjects(), result.MissingSubjects...))
  177. result.Operation = ReconcileUpdate
  178. case removeExtraSubjects && (len(result.MissingSubjects) > 0 || len(result.ExtraSubjects) > 0):
  179. // stomp to expected subjects in the non-union case
  180. result.RoleBinding.SetSubjects(expected.GetSubjects())
  181. result.Operation = ReconcileUpdate
  182. }
  183. return result, nil
  184. }
  185. func contains(list []rbacv1.Subject, item rbacv1.Subject) bool {
  186. for _, listItem := range list {
  187. if listItem == item {
  188. return true
  189. }
  190. }
  191. return false
  192. }
  193. // diffSubjectLists returns lists containing the items unique to each provided list:
  194. // list1Only = list1 - list2
  195. // list2Only = list2 - list1
  196. // if both returned lists are empty, the provided lists are equal
  197. func diffSubjectLists(list1 []rbacv1.Subject, list2 []rbacv1.Subject) (list1Only []rbacv1.Subject, list2Only []rbacv1.Subject) {
  198. for _, list1Item := range list1 {
  199. if !contains(list2, list1Item) {
  200. if !contains(list1Only, list1Item) {
  201. list1Only = append(list1Only, list1Item)
  202. }
  203. }
  204. }
  205. for _, list2Item := range list2 {
  206. if !contains(list1, list2Item) {
  207. if !contains(list2Only, list2Item) {
  208. list2Only = append(list2Only, list2Item)
  209. }
  210. }
  211. }
  212. return
  213. }