github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/resources/clusterrolebinding.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resources 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/juju/errors" 11 corev1 "k8s.io/api/core/v1" 12 rbacv1 "k8s.io/api/rbac/v1" 13 k8serrors "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 16 "k8s.io/apimachinery/pkg/runtime" 17 "k8s.io/apimachinery/pkg/types" 18 "k8s.io/client-go/kubernetes" 19 "k8s.io/utils/pointer" 20 21 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 22 "github.com/juju/juju/caas/kubernetes/provider/utils" 23 "github.com/juju/juju/core/status" 24 ) 25 26 // ClusterRoleBinding extends the k8s cluster role binding. 27 type ClusterRoleBinding struct { 28 rbacv1.ClusterRoleBinding 29 } 30 31 // NewClusterRoleBinding creates a new role resource. 32 func NewClusterRoleBinding(name string, in *rbacv1.ClusterRoleBinding) *ClusterRoleBinding { 33 if in == nil { 34 in = &rbacv1.ClusterRoleBinding{} 35 } 36 in.SetName(name) 37 return &ClusterRoleBinding{*in} 38 } 39 40 // Clone returns a copy of the resource. 41 func (rb *ClusterRoleBinding) Clone() Resource { 42 clone := *rb 43 return &clone 44 } 45 46 // ID returns a comparable ID for the Resource 47 func (rb *ClusterRoleBinding) ID() ID { 48 return ID{"ClusterRoleBinding", rb.Name, rb.Namespace} 49 } 50 51 // Apply patches the resource change. 52 func (rb *ClusterRoleBinding) Apply(ctx context.Context, client kubernetes.Interface) error { 53 api := client.RbacV1().ClusterRoleBindings() 54 data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, &rb.ClusterRoleBinding) 55 if err != nil { 56 return errors.Trace(err) 57 } 58 res, err := api.Patch(ctx, rb.Name, types.StrategicMergePatchType, data, metav1.PatchOptions{ 59 FieldManager: JujuFieldManager, 60 }) 61 if k8serrors.IsNotFound(err) { 62 res, err = api.Create(ctx, &rb.ClusterRoleBinding, metav1.CreateOptions{ 63 FieldManager: JujuFieldManager, 64 }) 65 } 66 if k8serrors.IsConflict(err) { 67 return errors.Annotatef(errConflict, "cluster role binding %q", rb.Name) 68 } 69 if err != nil { 70 return errors.Trace(err) 71 } 72 rb.ClusterRoleBinding = *res 73 return nil 74 } 75 76 // Get refreshes the resource. 77 func (rb *ClusterRoleBinding) Get(ctx context.Context, client kubernetes.Interface) error { 78 api := client.RbacV1().ClusterRoleBindings() 79 res, err := api.Get(ctx, rb.Name, metav1.GetOptions{}) 80 if k8serrors.IsNotFound(err) { 81 return errors.NewNotFound(err, "k8s") 82 } else if err != nil { 83 return errors.Trace(err) 84 } 85 rb.ClusterRoleBinding = *res 86 return nil 87 } 88 89 // Delete removes the resource. 90 func (rb *ClusterRoleBinding) Delete(ctx context.Context, client kubernetes.Interface) error { 91 api := client.RbacV1().ClusterRoleBindings() 92 err := api.Delete(ctx, rb.Name, metav1.DeleteOptions{ 93 PropagationPolicy: k8sconstants.DeletePropagationBackground(), 94 GracePeriodSeconds: pointer.Int64Ptr(0), 95 Preconditions: utils.NewUIDPreconditions(rb.UID), 96 }) 97 if k8serrors.IsNotFound(err) { 98 return nil 99 } else if err != nil { 100 return errors.Trace(err) 101 } 102 return nil 103 } 104 105 // shouldDelete checks if there are any changes in the immutable field to decide 106 // if the existing cluster role binding should be deleted or not. 107 func shouldDelete(existing, new rbacv1.ClusterRoleBinding) bool { 108 return existing.RoleRef.APIGroup != new.RoleRef.APIGroup || 109 existing.RoleRef.Kind != new.RoleRef.Kind || 110 existing.RoleRef.Name != new.RoleRef.Name 111 } 112 113 // Ensure ensures this cluster role exists in it's desired form inside the 114 // cluster. If the object does not exist it's updated and if the object exists 115 // it's updated. The method also takes an optional set of claims to test the 116 // existing Kubernetes object with to assert ownership before overwriting it. 117 func (rb *ClusterRoleBinding) Ensure( 118 ctx context.Context, 119 client kubernetes.Interface, 120 claims ...Claim, 121 ) ([]func(), error) { 122 // TODO(caas): roll this into Apply() 123 cleanups := []func(){} 124 125 existing := ClusterRoleBinding{rb.ClusterRoleBinding} 126 err := existing.Get(ctx, client) 127 if err != nil && !errors.IsNotFound(err) { 128 return cleanups, errors.Annotatef(err, "getting existing cluster role binding %q", rb.Name) 129 } 130 doUpdate := err == nil 131 if err == nil { 132 hasClaim, err := RunClaims(claims...).Assert(&existing.ClusterRoleBinding) 133 if err != nil { 134 return cleanups, errors.Annotatef(err, "checking for existing cluster role binding %q", rb.Name) 135 } 136 if !hasClaim { 137 return cleanups, errors.AlreadyExistsf( 138 "cluster role binding %q not controlled by juju", rb.Name) 139 } 140 if shouldDelete(existing.ClusterRoleBinding, rb.ClusterRoleBinding) { 141 // RoleRef is immutable, delete the cluster role binding then re-create it. 142 if err := existing.Delete(ctx, client); err != nil { 143 return cleanups, errors.Annotatef( 144 err, 145 "delete cluster role binding %q because roleref has changed", 146 existing.Name) 147 } 148 doUpdate = false 149 } 150 } 151 152 cleanups = append(cleanups, func() { _ = rb.Delete(ctx, client) }) 153 if !doUpdate { 154 return cleanups, rb.Apply(ctx, client) 155 } else if err := rb.Update(ctx, client); err != nil { 156 return cleanups, err 157 } 158 return cleanups, nil 159 } 160 161 // Events emitted by the resource. 162 func (rb *ClusterRoleBinding) Events(ctx context.Context, client kubernetes.Interface) ([]corev1.Event, error) { 163 return ListEventsForObject(ctx, client, rb.Namespace, rb.Name, "ClusterRoleBinding") 164 } 165 166 // ComputeStatus returns a juju status for the resource. 167 func (rb *ClusterRoleBinding) ComputeStatus(_ context.Context, _ kubernetes.Interface, now time.Time) (string, status.Status, time.Time, error) { 168 if rb.DeletionTimestamp != nil { 169 return "", status.Terminated, rb.DeletionTimestamp.Time, nil 170 } 171 return "", status.Active, now, nil 172 } 173 174 // Update updates the object in the Kubernetes cluster to the new representation 175 func (rb *ClusterRoleBinding) Update(ctx context.Context, client kubernetes.Interface) error { 176 out, err := client.RbacV1().ClusterRoleBindings().Update( 177 ctx, 178 &rb.ClusterRoleBinding, 179 metav1.UpdateOptions{ 180 FieldManager: JujuFieldManager, 181 }, 182 ) 183 if k8serrors.IsNotFound(err) { 184 return errors.Annotatef(err, "updating cluster role binding %q", rb.Name) 185 } else if err != nil { 186 return errors.Trace(err) 187 } 188 rb.ClusterRoleBinding = *out 189 return nil 190 }