github.com/spotahome/redis-operator@v1.2.4/service/k8s/rbac.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  
     6  	rbacv1 "k8s.io/api/rbac/v1"
     7  	"k8s.io/apimachinery/pkg/api/errors"
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  	"k8s.io/client-go/kubernetes"
    10  
    11  	"github.com/spotahome/redis-operator/log"
    12  	"github.com/spotahome/redis-operator/metrics"
    13  )
    14  
    15  // RBAC is the service that knows how to interact with k8s to manage RBAC related resources.
    16  type RBAC interface {
    17  	GetClusterRole(name string) (*rbacv1.ClusterRole, error)
    18  	GetRole(namespace, name string) (*rbacv1.Role, error)
    19  	GetRoleBinding(namespace, name string) (*rbacv1.RoleBinding, error)
    20  	CreateRole(namespace string, role *rbacv1.Role) error
    21  	UpdateRole(namespace string, role *rbacv1.Role) error
    22  	CreateOrUpdateRole(namespace string, binding *rbacv1.Role) error
    23  	CreateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error
    24  	UpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error
    25  	CreateOrUpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error
    26  }
    27  
    28  // NamespaceService is the Namespace service implementation using API calls to kubernetes.
    29  type RBACService struct {
    30  	kubeClient      kubernetes.Interface
    31  	logger          log.Logger
    32  	metricsRecorder metrics.Recorder
    33  }
    34  
    35  // NewRBACService returns a new RBAC KubeService.
    36  func NewRBACService(kubeClient kubernetes.Interface, logger log.Logger, metricsRecorder metrics.Recorder) *RBACService {
    37  	logger = logger.With("service", "k8s.rbac")
    38  	return &RBACService{
    39  		kubeClient:      kubeClient,
    40  		logger:          logger,
    41  		metricsRecorder: metricsRecorder,
    42  	}
    43  }
    44  
    45  func (r *RBACService) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
    46  	clusterRole, err := r.kubeClient.RbacV1().ClusterRoles().Get(context.TODO(), name, metav1.GetOptions{})
    47  	recordMetrics(metrics.NOT_APPLICABLE, "ClusterRole", name, "GET", err, r.metricsRecorder)
    48  	return clusterRole, err
    49  }
    50  
    51  func (r *RBACService) GetRole(namespace, name string) (*rbacv1.Role, error) {
    52  	role, err := r.kubeClient.RbacV1().Roles(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    53  	recordMetrics(namespace, "Role", name, "GET", err, r.metricsRecorder)
    54  	return role, err
    55  }
    56  
    57  func (r *RBACService) GetRoleBinding(namespace, name string) (*rbacv1.RoleBinding, error) {
    58  	rolbinding, err := r.kubeClient.RbacV1().RoleBindings(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    59  	recordMetrics(namespace, "RoleBinding", name, "GET", err, r.metricsRecorder)
    60  	return rolbinding, err
    61  }
    62  
    63  func (r *RBACService) DeleteRole(namespace, name string) error {
    64  	err := r.kubeClient.RbacV1().Roles(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
    65  	recordMetrics(namespace, "Role", name, "DELETE", err, r.metricsRecorder)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	r.logger.WithField("namespace", namespace).WithField("role", name).Debugf("role deleted")
    70  	return nil
    71  }
    72  
    73  func (r *RBACService) CreateRole(namespace string, role *rbacv1.Role) error {
    74  	_, err := r.kubeClient.RbacV1().Roles(namespace).Create(context.TODO(), role, metav1.CreateOptions{})
    75  	recordMetrics(namespace, "Role", role.GetName(), "CREATE", err, r.metricsRecorder)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	r.logger.WithField("namespace", namespace).WithField("role", role.Name).Debugf("role created")
    80  	return nil
    81  }
    82  
    83  func (s *RBACService) UpdateRole(namespace string, role *rbacv1.Role) error {
    84  	_, err := s.kubeClient.RbacV1().Roles(namespace).Update(context.TODO(), role, metav1.UpdateOptions{})
    85  	recordMetrics(namespace, "Role", role.GetName(), "UPDATE", err, s.metricsRecorder)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	s.logger.WithField("namespace", namespace).WithField("role", role.ObjectMeta.Name).Debugf("role updated")
    90  	return err
    91  }
    92  
    93  func (r *RBACService) CreateOrUpdateRole(namespace string, role *rbacv1.Role) error {
    94  	storedRole, err := r.GetRole(namespace, role.Name)
    95  	if err != nil {
    96  		// If no resource we need to create.
    97  		if errors.IsNotFound(err) {
    98  			return r.CreateRole(namespace, role)
    99  		}
   100  		return err
   101  	}
   102  
   103  	// Already exists, need to Update.
   104  	// Set the correct resource version to ensure we are on the latest version. This way the only valid
   105  	// namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency),
   106  	// we will replace the current namespace state.
   107  	role.ResourceVersion = storedRole.ResourceVersion
   108  	return r.UpdateRole(namespace, role)
   109  }
   110  
   111  func (r *RBACService) DeleteRoleBinding(namespace, name string) error {
   112  	err := r.kubeClient.RbacV1().RoleBindings(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
   113  	recordMetrics(namespace, "RoleBinding", name, "DELETE", err, r.metricsRecorder)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	r.logger.WithField("namespace", namespace).WithField("binding", name).Debugf("role binding deleted")
   118  	return nil
   119  }
   120  
   121  func (r *RBACService) CreateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error {
   122  	_, err := r.kubeClient.RbacV1().RoleBindings(namespace).Create(context.TODO(), binding, metav1.CreateOptions{})
   123  	recordMetrics(namespace, "RoleBinding", binding.GetName(), "CREATE", err, r.metricsRecorder)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	r.logger.WithField("namespace", namespace).WithField("binding", binding.Name).Debugf("role binding created")
   128  	return nil
   129  }
   130  
   131  func (r *RBACService) UpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error {
   132  	_, err := r.kubeClient.RbacV1().RoleBindings(namespace).Update(context.TODO(), binding, metav1.UpdateOptions{})
   133  	recordMetrics(namespace, "Role", binding.GetName(), "UPDATE", err, r.metricsRecorder)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	r.logger.WithField("namespace", namespace).WithField("binding", binding.Name).Debugf("role binding updated")
   138  	return nil
   139  }
   140  
   141  func (r *RBACService) CreateOrUpdateRoleBinding(namespace string, binding *rbacv1.RoleBinding) error {
   142  	storedBinding, err := r.GetRoleBinding(namespace, binding.Name)
   143  	if err != nil {
   144  		// If no resource we need to create.
   145  		if errors.IsNotFound(err) {
   146  			return r.CreateRoleBinding(namespace, binding)
   147  		}
   148  		return err
   149  	}
   150  
   151  	// Check if the role ref has changed, roleref updates are not allowed, if changed then delete and create again the role binding.
   152  	// https://github.com/kubernetes/kubernetes/blob/0f0a5223dfc75337d03c9b80ae552ae8ef138eeb/pkg/apis/rbac/validation/validation.go#L157-L159
   153  	if storedBinding.RoleRef != binding.RoleRef {
   154  		r.logger.WithField("namespace", namespace).WithField("binding", binding.Name).Infof("roleref changed, need to recreate role binding resource")
   155  		if err := r.DeleteRoleBinding(namespace, binding.Name); err != nil {
   156  			return err
   157  		}
   158  		return r.CreateRoleBinding(namespace, binding)
   159  	}
   160  
   161  	// Already exists, need to Update.
   162  	// Set the correct resource version to ensure we are on the latest version. This way the only valid
   163  	// namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency),
   164  	// we will replace the current namespace state.
   165  	binding.ResourceVersion = storedBinding.ResourceVersion
   166  	return r.UpdateRoleBinding(namespace, binding)
   167  }