github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/resources/serviceaccount.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 k8serrors "k8s.io/apimachinery/pkg/api/errors" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 "k8s.io/apimachinery/pkg/runtime" 16 "k8s.io/apimachinery/pkg/types" 17 "k8s.io/client-go/kubernetes" 18 19 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 20 "github.com/juju/juju/core/status" 21 ) 22 23 // ServiceAccount extends the k8s service account. 24 type ServiceAccount struct { 25 corev1.ServiceAccount 26 } 27 28 // NewServiceAccount creates a new service account resource. 29 func NewServiceAccount(name string, namespace string, in *corev1.ServiceAccount) *ServiceAccount { 30 if in == nil { 31 in = &corev1.ServiceAccount{} 32 } 33 in.SetName(name) 34 in.SetNamespace(namespace) 35 return &ServiceAccount{*in} 36 } 37 38 // Clone returns a copy of the resource. 39 func (sa *ServiceAccount) Clone() Resource { 40 clone := *sa 41 return &clone 42 } 43 44 // ID returns a comparable ID for the Resource 45 func (sa *ServiceAccount) ID() ID { 46 return ID{"ServiceAccount", sa.Name, sa.Namespace} 47 } 48 49 // Apply patches the resource change. 50 func (sa *ServiceAccount) Apply(ctx context.Context, client kubernetes.Interface) error { 51 api := client.CoreV1().ServiceAccounts(sa.Namespace) 52 data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, &sa.ServiceAccount) 53 if err != nil { 54 return errors.Trace(err) 55 } 56 res, err := api.Patch(ctx, sa.Name, types.StrategicMergePatchType, data, metav1.PatchOptions{ 57 FieldManager: JujuFieldManager, 58 }) 59 if k8serrors.IsNotFound(err) { 60 res, err = api.Create(ctx, &sa.ServiceAccount, metav1.CreateOptions{ 61 FieldManager: JujuFieldManager, 62 }) 63 } 64 if k8serrors.IsConflict(err) { 65 return errors.Annotatef(errConflict, "service account %q", sa.Name) 66 } 67 if err != nil { 68 return errors.Trace(err) 69 } 70 sa.ServiceAccount = *res 71 return nil 72 } 73 74 // Get refreshes the resource. 75 func (sa *ServiceAccount) Get(ctx context.Context, client kubernetes.Interface) error { 76 api := client.CoreV1().ServiceAccounts(sa.Namespace) 77 res, err := api.Get(ctx, sa.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 sa.ServiceAccount = *res 84 return nil 85 } 86 87 // Delete removes the resource. 88 func (sa *ServiceAccount) Delete(ctx context.Context, client kubernetes.Interface) error { 89 api := client.CoreV1().ServiceAccounts(sa.Namespace) 90 err := api.Delete(ctx, sa.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 func (sa *ServiceAccount) Ensure( 102 ctx context.Context, 103 client kubernetes.Interface, 104 claims ...Claim, 105 ) ([]func(), error) { 106 alreadyExists := false 107 cleanups := []func(){} 108 hasClaim := true 109 110 existing := ServiceAccount{sa.ServiceAccount} 111 err := existing.Get(ctx, client) 112 if err != nil && !errors.IsNotFound(err) { 113 return cleanups, errors.Annotatef( 114 err, 115 "checking for existing service account %q", 116 existing.Name, 117 ) 118 } 119 if err == nil { 120 alreadyExists = true 121 hasClaim, err = RunClaims(claims...).Assert(&existing.ServiceAccount) 122 if err != nil { 123 return cleanups, errors.Annotatef( 124 err, 125 "checking claims for service account %q", 126 existing.Name, 127 ) 128 } 129 } 130 131 if !hasClaim { 132 return cleanups, errors.AlreadyExistsf( 133 "service account %q not controlled by juju", sa.Name) 134 } 135 136 cleanups = append(cleanups, func() { _ = sa.Delete(ctx, client) }) 137 if !alreadyExists { 138 return cleanups, sa.Apply(ctx, client) 139 } 140 141 return cleanups, errors.Trace(sa.Update(ctx, client)) 142 } 143 144 // Events emitted by the resource. 145 func (sa *ServiceAccount) Events(ctx context.Context, client kubernetes.Interface) ([]corev1.Event, error) { 146 return ListEventsForObject(ctx, client, sa.Namespace, sa.Name, "ServiceAccount") 147 } 148 149 // ComputeStatus returns a juju status for the resource. 150 func (sa *ServiceAccount) ComputeStatus(_ context.Context, _ kubernetes.Interface, now time.Time) (string, status.Status, time.Time, error) { 151 if sa.DeletionTimestamp != nil { 152 return "", status.Terminated, sa.DeletionTimestamp.Time, nil 153 } 154 return "", status.Active, now, nil 155 } 156 157 func (sa *ServiceAccount) Update( 158 ctx context.Context, 159 client kubernetes.Interface, 160 ) error { 161 out, err := client.CoreV1().ServiceAccounts(sa.Namespace).Update( 162 ctx, 163 &sa.ServiceAccount, 164 metav1.UpdateOptions{ 165 FieldManager: JujuFieldManager, 166 }, 167 ) 168 if k8serrors.IsNotFound(err) { 169 return errors.NewNotFound(err, "updating service account") 170 } else if err != nil { 171 return errors.Trace(err) 172 } 173 sa.ServiceAccount = *out 174 return nil 175 }