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  }