github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/resources/clusterrole.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 20 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 21 "github.com/juju/juju/core/status" 22 ) 23 24 // ClusterRole extends the k8s cluster role. 25 type ClusterRole struct { 26 rbacv1.ClusterRole 27 } 28 29 // NewClusterRole creates a new cluster role resource. 30 func NewClusterRole(name string, in *rbacv1.ClusterRole) *ClusterRole { 31 if in == nil { 32 in = &rbacv1.ClusterRole{} 33 } 34 in.SetName(name) 35 return &ClusterRole{*in} 36 } 37 38 // Clone returns a copy of the resource. 39 func (r *ClusterRole) Clone() Resource { 40 clone := *r 41 return &clone 42 } 43 44 // ID returns a comparable ID for the Resource 45 func (r *ClusterRole) ID() ID { 46 return ID{"ClusterRole", r.Name, r.Namespace} 47 } 48 49 // Apply patches the resource change. 50 func (r *ClusterRole) Apply(ctx context.Context, client kubernetes.Interface) error { 51 api := client.RbacV1().ClusterRoles() 52 data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, &r.ClusterRole) 53 if err != nil { 54 return errors.Trace(err) 55 } 56 res, err := api.Patch(ctx, r.Name, types.StrategicMergePatchType, data, metav1.PatchOptions{ 57 FieldManager: JujuFieldManager, 58 }) 59 if k8serrors.IsNotFound(err) { 60 res, err = api.Create(ctx, &r.ClusterRole, metav1.CreateOptions{ 61 FieldManager: JujuFieldManager, 62 }) 63 } 64 if k8serrors.IsConflict(err) { 65 return errors.Annotatef(errConflict, "cluster role %q", r.Name) 66 } 67 if err != nil { 68 return errors.Trace(err) 69 } 70 r.ClusterRole = *res 71 return nil 72 } 73 74 // Get refreshes the resource. 75 func (r *ClusterRole) Get(ctx context.Context, client kubernetes.Interface) error { 76 api := client.RbacV1().ClusterRoles() 77 res, err := api.Get(ctx, r.Name, metav1.GetOptions{}) 78 if k8serrors.IsNotFound(err) { 79 return errors.NewNotFound(err, "k8s") 80 } else if err != nil { 81 return errors.Trace(err) 82 } 83 r.ClusterRole = *res 84 return nil 85 } 86 87 // Delete removes the resource. 88 func (r *ClusterRole) Delete(ctx context.Context, client kubernetes.Interface) error { 89 api := client.RbacV1().ClusterRoles() 90 err := api.Delete(ctx, r.Name, metav1.DeleteOptions{ 91 PropagationPolicy: k8sconstants.DefaultPropagationPolicy(), 92 }) 93 if k8serrors.IsNotFound(err) { 94 return nil 95 } else if err != nil { 96 return errors.Trace(err) 97 } 98 return nil 99 } 100 101 // Ensure ensures this cluster role exists in it's desired form inside the 102 // cluster. If the object does not exist it's updated and if the object exists 103 // it's updated. The method also takes an optional set of claims to test the 104 // exisiting Kubernetes object with to assert ownership before overwriting it. 105 func (r *ClusterRole) Ensure( 106 ctx context.Context, 107 client kubernetes.Interface, 108 claims ...Claim, 109 ) ([]func(), error) { 110 // TODO(caas): roll this into Apply() 111 cleanups := []func(){} 112 hasClaim := true 113 114 existing := ClusterRole{r.ClusterRole} 115 err := existing.Get(ctx, client) 116 if err == nil { 117 hasClaim, err = RunClaims(claims...).Assert(&existing.ClusterRole) 118 } 119 if err != nil && !errors.IsNotFound(err) { 120 return cleanups, errors.Annotatef( 121 err, 122 "checking for existing cluster role %q", 123 existing.Name, 124 ) 125 } 126 127 if !hasClaim { 128 return cleanups, errors.AlreadyExistsf( 129 "cluster role %q not controlled by juju", r.Name) 130 } 131 132 cleanups = append(cleanups, func() { _ = r.Delete(ctx, client) }) 133 if errors.IsNotFound(err) { 134 return cleanups, r.Apply(ctx, client) 135 } 136 137 if err := r.Update(ctx, client); err != nil { 138 return cleanups, err 139 } 140 return cleanups, nil 141 } 142 143 // Events emitted by the resource. 144 func (r *ClusterRole) Events(ctx context.Context, client kubernetes.Interface) ([]corev1.Event, error) { 145 return ListEventsForObject(ctx, client, r.Namespace, r.Name, "ClusterRole") 146 } 147 148 // ComputeStatus returns a juju status for the resource. 149 func (r *ClusterRole) ComputeStatus(_ context.Context, _ kubernetes.Interface, now time.Time) (string, status.Status, time.Time, error) { 150 if r.DeletionTimestamp != nil { 151 return "", status.Terminated, r.DeletionTimestamp.Time, nil 152 } 153 return "", status.Active, now, nil 154 } 155 156 // Update updates the object in the Kubernetes cluster to the new representation 157 func (r *ClusterRole) Update(ctx context.Context, client kubernetes.Interface) error { 158 out, err := client.RbacV1().ClusterRoles().Update( 159 ctx, 160 &r.ClusterRole, 161 metav1.UpdateOptions{ 162 FieldManager: JujuFieldManager, 163 }, 164 ) 165 if k8serrors.IsNotFound(err) { 166 return errors.NewNotFound(err, "updating cluster role") 167 } else if err != nil { 168 return errors.Trace(err) 169 } 170 r.ClusterRole = *out 171 return nil 172 }