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  }