github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/modeloperator.go (about)

     1  // Copyright 2020 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"path/filepath"
    10  	"strconv"
    11  
    12  	jujuclock "github.com/juju/clock"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names/v5"
    15  	apps "k8s.io/api/apps/v1"
    16  	core "k8s.io/api/core/v1"
    17  	rbac "k8s.io/api/rbac/v1"
    18  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    19  	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/labels"
    21  	"k8s.io/apimachinery/pkg/util/intstr"
    22  	"k8s.io/client-go/kubernetes"
    23  	"k8s.io/utils/pointer"
    24  
    25  	"github.com/juju/juju/agent"
    26  	"github.com/juju/juju/caas"
    27  	"github.com/juju/juju/caas/kubernetes/provider/constants"
    28  	"github.com/juju/juju/caas/kubernetes/provider/proxy"
    29  	"github.com/juju/juju/caas/kubernetes/provider/resources"
    30  	"github.com/juju/juju/caas/kubernetes/provider/utils"
    31  	"github.com/juju/juju/core/paths"
    32  	coreresources "github.com/juju/juju/core/resources"
    33  )
    34  
    35  // ModelOperatorBroker defines a broker for Executing Kubernetes ensure
    36  // commands. This interfaces is scoped down to the exact components needed by
    37  // the ensure model operator routines.
    38  type ModelOperatorBroker interface {
    39  	// Client returns the Kubernetes client to use for model operator actions.
    40  	Client() kubernetes.Interface
    41  
    42  	// EnsureConfigMap ensures the supplied kubernetes config map exists in the
    43  	// targeted cluster. Error returned if this action is not able to be
    44  	// performed.
    45  	EnsureConfigMap(*core.ConfigMap) ([]func(), error)
    46  
    47  	// EnsureDeployment ensures the supplied kubernetes deployment object exists
    48  	// in the targeted cluster. Error returned if this action is not able to be
    49  	// performed.
    50  	EnsureDeployment(*apps.Deployment) ([]func(), error)
    51  
    52  	// EnsureRole ensures the supplied kubernetes role object exists in the
    53  	// targeted clusters namespace
    54  	EnsureRole(*rbac.Role) ([]func(), error)
    55  
    56  	// EnsureRoleBinding ensures the supplied kubernetes role binding object
    57  	// exists in the targetes clusters namespace
    58  	EnsureRoleBinding(*rbac.RoleBinding) ([]func(), error)
    59  
    60  	// EnsureService ensures the spplied kubernetes service object exists in the
    61  	// targeted cluster. Error returned if the action is not able to be
    62  	// performed.
    63  	EnsureService(*core.Service) ([]func(), error)
    64  
    65  	// EnsureServiceAccount ensures the supplied the kubernetes service account
    66  	// exists in the targets cluster.
    67  	EnsureServiceAccount(*core.ServiceAccount) ([]func(), error)
    68  
    69  	// Model returns the name of the current model being deployed to for the
    70  	// broker
    71  	Model() string
    72  
    73  	// Namespace returns the current default namespace targeted by this broker.
    74  	Namespace() string
    75  
    76  	// IsLegacyLabels indicates if this provider is operating on a legacy label schema
    77  	IsLegacyLabels() bool
    78  }
    79  
    80  // modelOperatorBrokerBridge provides a pluggable struct of funcs to implement
    81  // the ModelOperatorBroker interface
    82  type modelOperatorBrokerBridge struct {
    83  	client               kubernetes.Interface
    84  	ensureConfigMap      func(*core.ConfigMap) ([]func(), error)
    85  	ensureDeployment     func(*apps.Deployment) ([]func(), error)
    86  	ensureRole           func(*rbac.Role) ([]func(), error)
    87  	ensureRoleBinding    func(*rbac.RoleBinding) ([]func(), error)
    88  	ensureService        func(*core.Service) ([]func(), error)
    89  	ensureServiceAccount func(*core.ServiceAccount) ([]func(), error)
    90  	model                func() string
    91  	namespace            func() string
    92  	isLegacyLabels       func() bool
    93  }
    94  
    95  const (
    96  	modelOperatorPortLabel = "api"
    97  
    98  	EnvModelAgentCAASServiceName      = "SERVICE_NAME"
    99  	EnvModelAgentCAASServiceNamespace = "SERVICE_NAMESPACE"
   100  	EnvModelAgentHTTPPort             = "HTTP_PORT"
   101  
   102  	OperatorModelTarget = "model"
   103  )
   104  
   105  var (
   106  	// modelOperatorName is the model operator stack name used for deployment, service, RBAC resources.
   107  	modelOperatorName = "modeloperator"
   108  
   109  	// ExecRBACResourceName is the model's exec RBAC resource name.
   110  	ExecRBACResourceName = "model-exec"
   111  )
   112  
   113  // Client implements ModelOperatorBroker
   114  func (m *modelOperatorBrokerBridge) Client() kubernetes.Interface {
   115  	return m.client
   116  }
   117  
   118  // EnsureConfigMap implements ModelOperatorBroker
   119  func (m *modelOperatorBrokerBridge) EnsureConfigMap(c *core.ConfigMap) ([]func(), error) {
   120  	if m.ensureConfigMap == nil {
   121  		return []func(){}, errors.NotImplementedf("ensure config map bridge")
   122  	}
   123  	return m.ensureConfigMap(c)
   124  }
   125  
   126  // EnsureDeployment implements ModelOperatorBroker
   127  func (m *modelOperatorBrokerBridge) EnsureDeployment(d *apps.Deployment) ([]func(), error) {
   128  	if m.ensureDeployment == nil {
   129  		return []func(){}, errors.NotImplementedf("ensure deployment bridge")
   130  	}
   131  	return m.ensureDeployment(d)
   132  }
   133  
   134  // EnsureRole implements ModelOperatorBroker
   135  func (m *modelOperatorBrokerBridge) EnsureRole(r *rbac.Role) ([]func(), error) {
   136  	if m.ensureRole == nil {
   137  		return []func(){}, errors.NotImplementedf("ensure role bridge")
   138  	}
   139  	return m.ensureRole(r)
   140  }
   141  
   142  // EnsureRoleBinding implements ModelOperatorBroker
   143  func (m *modelOperatorBrokerBridge) EnsureRoleBinding(r *rbac.RoleBinding) ([]func(), error) {
   144  	if m.ensureRoleBinding == nil {
   145  		return []func(){}, errors.NotImplementedf("ensure role binding bridge")
   146  	}
   147  	return m.ensureRoleBinding(r)
   148  }
   149  
   150  // EnsureService implements ModelOperatorBroker
   151  func (m *modelOperatorBrokerBridge) EnsureService(s *core.Service) ([]func(), error) {
   152  	if m.ensureService == nil {
   153  		return []func(){}, errors.NotImplementedf("ensure service bridge")
   154  	}
   155  	return m.ensureService(s)
   156  }
   157  
   158  // EnsureServiceAccount implements ModelOperatorBroker
   159  func (m *modelOperatorBrokerBridge) EnsureServiceAccount(s *core.ServiceAccount) ([]func(), error) {
   160  	if m.ensureServiceAccount == nil {
   161  		return []func(){}, errors.NotImplementedf("ensure service account bridge")
   162  	}
   163  	return m.ensureServiceAccount(s)
   164  }
   165  
   166  // Model implements ModelOperatorBroker
   167  func (m *modelOperatorBrokerBridge) Model() string {
   168  	if m.model == nil {
   169  		return ""
   170  	}
   171  	return m.model()
   172  }
   173  
   174  // Namespace implements ModelOperatorBroker
   175  func (m *modelOperatorBrokerBridge) Namespace() string {
   176  	if m.namespace == nil {
   177  		return ""
   178  	}
   179  	return m.namespace()
   180  }
   181  
   182  func (m *modelOperatorBrokerBridge) IsLegacyLabels() bool {
   183  	if m.isLegacyLabels == nil {
   184  		return true
   185  	}
   186  	return m.isLegacyLabels()
   187  }
   188  
   189  func ensureModelOperator(
   190  	modelUUID,
   191  	agentPath string,
   192  	clock jujuclock.Clock,
   193  	config *caas.ModelOperatorConfig,
   194  	broker ModelOperatorBroker,
   195  ) (err error) {
   196  
   197  	ctx := context.TODO()
   198  	operatorName := modelOperatorName
   199  	modelTag := names.NewModelTag(modelUUID)
   200  
   201  	selectorLabels := modelOperatorLabels(operatorName, broker.IsLegacyLabels())
   202  	labels := selectorLabels
   203  	if !broker.IsLegacyLabels() {
   204  		labels = utils.LabelsMerge(labels, utils.LabelsJuju)
   205  	}
   206  
   207  	cleanUpFuncs := []func(){}
   208  	defer func() {
   209  		if err != nil {
   210  			utils.RunCleanUps(cleanUpFuncs)
   211  		}
   212  	}()
   213  
   214  	configMap := modelOperatorConfigMap(
   215  		broker.Namespace(),
   216  		operatorName,
   217  		labels,
   218  		config.AgentConf)
   219  
   220  	c, err := broker.EnsureConfigMap(configMap)
   221  	cleanUpFuncs = append(cleanUpFuncs, c...)
   222  	if err != nil {
   223  		return errors.Annotate(err, "ensuring model operator config map")
   224  	}
   225  
   226  	volumes := []core.Volume{{
   227  		Name: configMap.Name,
   228  		VolumeSource: core.VolumeSource{
   229  			ConfigMap: &core.ConfigMapVolumeSource{
   230  				LocalObjectReference: core.LocalObjectReference{
   231  					Name: configMap.Name,
   232  				},
   233  				Items: []core.KeyToPath{
   234  					{
   235  						Key:  modelOperatorConfigMapAgentConfKey(modelOperatorName),
   236  						Path: constants.TemplateFileNameAgentConf,
   237  					},
   238  				},
   239  			},
   240  		},
   241  	}}
   242  
   243  	volumeMounts := []core.VolumeMount{
   244  		{
   245  			Name:      configMap.Name,
   246  			MountPath: filepath.Join(agent.Dir(agentPath, modelTag), constants.TemplateFileNameAgentConf),
   247  			SubPath:   constants.TemplateFileNameAgentConf,
   248  		},
   249  	}
   250  
   251  	saName, c, err := ensureModelOperatorRBAC(
   252  		ctx,
   253  		broker,
   254  		clock,
   255  		operatorName,
   256  		labels,
   257  	)
   258  	cleanUpFuncs = append(cleanUpFuncs, c...)
   259  	if err != nil {
   260  		return errors.Trace(err)
   261  	}
   262  
   263  	service := modelOperatorService(
   264  		operatorName, broker.Namespace(), labels, selectorLabels, config.Port)
   265  	c, err = broker.EnsureService(service)
   266  	cleanUpFuncs = append(cleanUpFuncs, c...)
   267  	if err != nil {
   268  		return errors.Annotate(err, "ensuring model operater service")
   269  	}
   270  
   271  	deployment, err := modelOperatorDeployment(
   272  		operatorName,
   273  		broker.Namespace(),
   274  		labels,
   275  		selectorLabels,
   276  		config.ImageDetails,
   277  		config.Port,
   278  		modelUUID,
   279  		service.Name,
   280  		saName,
   281  		volumes,
   282  		volumeMounts)
   283  	if err != nil {
   284  		return errors.Annotate(err, "building juju model operator deployment")
   285  	}
   286  
   287  	c, err = broker.EnsureDeployment(deployment)
   288  	cleanUpFuncs = append(cleanUpFuncs, c...)
   289  	if err != nil {
   290  		return errors.Annotate(err, "ensuring juju model operator deployment")
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // EnsureModelOperator implements caas broker's interface. Function ensures that
   297  // a model operator for this broker's namespace exists within Kubernetes.
   298  func (k *kubernetesClient) EnsureModelOperator(
   299  	modelUUID,
   300  	agentPath string,
   301  	config *caas.ModelOperatorConfig,
   302  ) error {
   303  	if k.client() == nil {
   304  		return errors.New("kubernetes client cannot be nil")
   305  	}
   306  
   307  	bridge := &modelOperatorBrokerBridge{
   308  		client: k.client(),
   309  		ensureConfigMap: func(c *core.ConfigMap) ([]func(), error) {
   310  			cleanUp, err := k.ensureConfigMap(c)
   311  			return []func(){cleanUp}, err
   312  		},
   313  		ensureDeployment: func(d *apps.Deployment) ([]func(), error) {
   314  			return []func(){}, k.ensureDeployment(d)
   315  		},
   316  		ensureRole: func(r *rbac.Role) ([]func(), error) {
   317  			_, c, err := k.ensureRole(r)
   318  			return c, err
   319  		},
   320  		ensureRoleBinding: func(rb *rbac.RoleBinding) ([]func(), error) {
   321  			_, c, err := k.ensureRoleBinding(rb)
   322  			return c, err
   323  		},
   324  		ensureService: func(svc *core.Service) ([]func(), error) {
   325  			c, err := k.ensureK8sService(svc)
   326  			return []func(){c}, err
   327  		},
   328  		ensureServiceAccount: func(sa *core.ServiceAccount) ([]func(), error) {
   329  			_, c, err := k.ensureServiceAccount(sa)
   330  			return c, err
   331  		},
   332  		namespace:      func() string { return k.namespace },
   333  		model:          func() string { return k.CurrentModel() },
   334  		isLegacyLabels: k.IsLegacyLabels,
   335  	}
   336  
   337  	return ensureModelOperator(modelUUID, agentPath, k.clock, config, bridge)
   338  }
   339  
   340  // ModelOperator return the model operator config used to create the current
   341  // model operator for this broker
   342  func (k *kubernetesClient) ModelOperator() (*caas.ModelOperatorConfig, error) {
   343  	if k.namespace == "" {
   344  		return nil, errNoNamespace
   345  	}
   346  	operatorName := modelOperatorName
   347  	exists, err := k.ModelOperatorExists()
   348  	if err != nil {
   349  		return nil, errors.Trace(err)
   350  	}
   351  	if !exists {
   352  		return nil, errors.NotFoundf("model operator %s", operatorName)
   353  	}
   354  
   355  	modelOperatorCfg := caas.ModelOperatorConfig{}
   356  	cm, err := k.client().CoreV1().ConfigMaps(k.namespace).
   357  		Get(context.TODO(), operatorName, meta.GetOptions{})
   358  	if err != nil && !k8serrors.IsNotFound(err) {
   359  		return nil, errors.Trace(err)
   360  	}
   361  	if cm != nil {
   362  		if agentConf, ok := cm.Data[modelOperatorConfigMapAgentConfKey(operatorName)]; ok {
   363  			modelOperatorCfg.AgentConf = []byte(agentConf)
   364  		}
   365  	}
   366  
   367  	return &modelOperatorCfg, nil
   368  }
   369  
   370  func modelOperatorConfigMap(
   371  	namespace,
   372  	operatorName string,
   373  	labels map[string]string,
   374  	agentConf []byte,
   375  ) *core.ConfigMap {
   376  
   377  	return &core.ConfigMap{
   378  		ObjectMeta: meta.ObjectMeta{
   379  			Name:      operatorName,
   380  			Namespace: namespace,
   381  			Labels:    labels,
   382  		},
   383  		Data: map[string]string{
   384  			modelOperatorConfigMapAgentConfKey(operatorName): string(agentConf),
   385  		},
   386  	}
   387  }
   388  
   389  func modelOperatorDeployment(
   390  	operatorName,
   391  	namespace string,
   392  	labels,
   393  	selectorLabels map[string]string,
   394  	operatorImageDetails coreresources.DockerImageDetails,
   395  	port int32,
   396  	modelUUID,
   397  	serviceName,
   398  	serviceAccountName string,
   399  	volumes []core.Volume,
   400  	volumeMounts []core.VolumeMount,
   401  ) (o *apps.Deployment, err error) {
   402  	jujudCmd := fmt.Sprintf("exec $JUJU_TOOLS_DIR/jujud model --model-uuid=%s", modelUUID)
   403  	jujuDataDir := paths.DataDir(paths.OSUnixLike)
   404  
   405  	o = &apps.Deployment{
   406  		ObjectMeta: meta.ObjectMeta{
   407  			Name:      operatorName,
   408  			Namespace: namespace,
   409  			Labels: utils.LabelsMerge(
   410  				labels,
   411  				utils.LabelsJujuModelOperatorDisableWebhook,
   412  			),
   413  		},
   414  		Spec: apps.DeploymentSpec{
   415  			Replicas: pointer.Int32Ptr(1),
   416  			Selector: &meta.LabelSelector{
   417  				MatchLabels: selectorLabels,
   418  			},
   419  			Template: core.PodTemplateSpec{
   420  				ObjectMeta: meta.ObjectMeta{
   421  					Labels: utils.LabelsMerge(
   422  						selectorLabels,
   423  						utils.LabelsJujuModelOperatorDisableWebhook,
   424  					),
   425  				},
   426  				Spec: core.PodSpec{
   427  					Containers: []core.Container{{
   428  						Image:           operatorImageDetails.RegistryPath,
   429  						ImagePullPolicy: core.PullIfNotPresent,
   430  						Name:            operatorContainerName,
   431  						WorkingDir:      jujuDataDir,
   432  						Command: []string{
   433  							"/bin/sh",
   434  						},
   435  						Args: []string{
   436  							"-c",
   437  							fmt.Sprintf(
   438  								caas.JujudStartUpSh,
   439  								jujuDataDir,
   440  								"tools",
   441  								jujudCmd,
   442  							),
   443  						},
   444  						Env: []core.EnvVar{
   445  							{
   446  								Name:  EnvModelAgentHTTPPort,
   447  								Value: strconv.Itoa(int(port)),
   448  							},
   449  							{
   450  								Name:  EnvModelAgentCAASServiceName,
   451  								Value: serviceName,
   452  							},
   453  							{
   454  								Name:  EnvModelAgentCAASServiceNamespace,
   455  								Value: namespace,
   456  							},
   457  						},
   458  						Ports: []core.ContainerPort{
   459  							{
   460  								ContainerPort: port,
   461  								Name:          modelOperatorPortLabel,
   462  								Protocol:      core.ProtocolTCP,
   463  							},
   464  						},
   465  						VolumeMounts: volumeMounts,
   466  					}},
   467  					ServiceAccountName:           serviceAccountName,
   468  					AutomountServiceAccountToken: boolPtr(true),
   469  					Volumes:                      volumes,
   470  				},
   471  			},
   472  		},
   473  	}
   474  	if operatorImageDetails.IsPrivate() {
   475  		o.Spec.Template.Spec.ImagePullSecrets = []core.LocalObjectReference{
   476  			{Name: constants.CAASImageRepoSecretName},
   477  		}
   478  	}
   479  	return o, nil
   480  }
   481  
   482  // ModelOperatorExists indicates if the model operator for the given broker
   483  // exists
   484  func (k *kubernetesClient) ModelOperatorExists() (bool, error) {
   485  	operatorName := modelOperatorName
   486  	exists, err := k.modelOperatorDeploymentExists(operatorName)
   487  	if err != nil {
   488  		return false, errors.Trace(err)
   489  	}
   490  	return exists, nil
   491  }
   492  
   493  func (k *kubernetesClient) modelOperatorDeploymentExists(operatorName string) (bool, error) {
   494  	if k.namespace == "" {
   495  		return false, errNoNamespace
   496  	}
   497  	_, err := k.client().AppsV1().Deployments(k.namespace).
   498  		Get(context.TODO(), operatorName, meta.GetOptions{})
   499  
   500  	if k8serrors.IsNotFound(err) {
   501  		return false, nil
   502  	}
   503  	if err != nil {
   504  		return false, errors.Trace(err)
   505  	}
   506  	return true, nil
   507  }
   508  
   509  func modelOperatorLabels(operatorName string, legacy bool) labels.Set {
   510  	if legacy {
   511  		return utils.LabelForKeyValue(constants.LegacyLabelModelOperator, operatorName)
   512  	}
   513  	return utils.LabelsForOperator(operatorName, OperatorModelTarget, legacy)
   514  }
   515  
   516  func modelOperatorService(
   517  	operatorName,
   518  	namespace string,
   519  	labels,
   520  	selectorLabels map[string]string,
   521  	port int32,
   522  ) *core.Service {
   523  	return &core.Service{
   524  		ObjectMeta: meta.ObjectMeta{
   525  			Name:      operatorName,
   526  			Namespace: namespace,
   527  			Labels:    labels,
   528  		},
   529  		Spec: core.ServiceSpec{
   530  			Selector: selectorLabels,
   531  			Type:     core.ServiceTypeClusterIP,
   532  			Ports: []core.ServicePort{
   533  				{
   534  					Protocol:   core.ProtocolTCP,
   535  					Port:       port,
   536  					TargetPort: intstr.FromString(modelOperatorPortLabel),
   537  				},
   538  			},
   539  		},
   540  	}
   541  }
   542  
   543  func modelOperatorGlobalScopedName(model, operatorName string) string {
   544  	if model == "" {
   545  		return operatorName
   546  	}
   547  	return fmt.Sprintf("%s-%s", model, operatorName)
   548  }
   549  
   550  func ensureModelOperatorRBAC(
   551  	ctx context.Context,
   552  	broker ModelOperatorBroker,
   553  	clock jujuclock.Clock,
   554  	operatorName string,
   555  	labels map[string]string,
   556  ) (string, []func(), error) {
   557  	cleanUpFuncs := []func(){}
   558  
   559  	objMetaGlobal := meta.ObjectMeta{
   560  		Name:   modelOperatorGlobalScopedName(broker.Model(), operatorName),
   561  		Labels: labels,
   562  	}
   563  	objMetaNamespaced := meta.ObjectMeta{
   564  		Name:      operatorName,
   565  		Labels:    labels,
   566  		Namespace: broker.Namespace(),
   567  	}
   568  
   569  	sa := &core.ServiceAccount{
   570  		ObjectMeta:                   objMetaNamespaced,
   571  		AutomountServiceAccountToken: boolPtr(true),
   572  	}
   573  
   574  	c, err := broker.EnsureServiceAccount(sa)
   575  	cleanUpFuncs = append(cleanUpFuncs, c...)
   576  	if err != nil {
   577  		return sa.Name, cleanUpFuncs, errors.Annotate(err, "ensuring service account")
   578  	}
   579  
   580  	clusterRole := resources.NewClusterRole(objMetaGlobal.GetName(), &rbac.ClusterRole{
   581  		ObjectMeta: objMetaGlobal,
   582  		Rules: []rbac.PolicyRule{
   583  			{
   584  				APIGroups: []string{""},
   585  				Resources: []string{"namespaces"},
   586  				Verbs:     []string{"get", "list"},
   587  			},
   588  			{
   589  				APIGroups: []string{"admissionregistration.k8s.io"},
   590  				Resources: []string{"mutatingwebhookconfigurations"},
   591  				Verbs: []string{
   592  					"create",
   593  					"delete",
   594  					"get",
   595  					"list",
   596  					"update",
   597  				},
   598  			},
   599  		},
   600  	})
   601  
   602  	c, err = clusterRole.Ensure(
   603  		ctx,
   604  		broker.Client(),
   605  		resources.ClaimJujuOwnership,
   606  	)
   607  	cleanUpFuncs = append(cleanUpFuncs, c...)
   608  	if err != nil {
   609  		return sa.Name, cleanUpFuncs, errors.Annotate(err, "ensuring cluster role")
   610  	}
   611  
   612  	clusterRoleBinding := resources.NewClusterRoleBinding(objMetaGlobal.GetName(), &rbac.ClusterRoleBinding{
   613  		ObjectMeta: objMetaGlobal,
   614  		RoleRef: rbac.RoleRef{
   615  			APIGroup: "rbac.authorization.k8s.io",
   616  			Kind:     "ClusterRole",
   617  			Name:     clusterRole.Name,
   618  		},
   619  		Subjects: []rbac.Subject{
   620  			{
   621  				Kind:      "ServiceAccount",
   622  				Name:      sa.Name,
   623  				Namespace: sa.Namespace,
   624  			},
   625  		},
   626  	})
   627  
   628  	c, err = clusterRoleBinding.Ensure(ctx, broker.Client(), resources.ClaimJujuOwnership)
   629  	cleanUpFuncs = append(cleanUpFuncs, c...)
   630  	if err != nil {
   631  		return sa.Name, cleanUpFuncs, errors.Annotate(err, "ensuring cluster role binding")
   632  	}
   633  
   634  	role := &rbac.Role{
   635  		ObjectMeta: objMetaNamespaced,
   636  		Rules: []rbac.PolicyRule{
   637  			{
   638  				APIGroups: []string{""},
   639  				Resources: []string{"serviceaccounts"},
   640  				Verbs: []string{
   641  					"get",
   642  					"list",
   643  					"watch",
   644  				},
   645  			},
   646  		},
   647  	}
   648  
   649  	c, err = broker.EnsureRole(role)
   650  	cleanUpFuncs = append(cleanUpFuncs, c...)
   651  	if err != nil {
   652  		return sa.Name, cleanUpFuncs, errors.Annotate(err, "ensuring role")
   653  	}
   654  
   655  	roleBinding := &rbac.RoleBinding{
   656  		ObjectMeta: objMetaNamespaced,
   657  		RoleRef: rbac.RoleRef{
   658  			APIGroup: "rbac.authorization.k8s.io",
   659  			Kind:     "Role",
   660  			Name:     role.Name,
   661  		},
   662  		Subjects: []rbac.Subject{
   663  			{
   664  				Kind:      "ServiceAccount",
   665  				Name:      sa.Name,
   666  				Namespace: sa.Namespace,
   667  			},
   668  		},
   669  	}
   670  
   671  	c, err = broker.EnsureRoleBinding(roleBinding)
   672  	cleanUpFuncs = append(cleanUpFuncs, c...)
   673  	if err != nil {
   674  		return sa.Name, cleanUpFuncs, errors.Annotate(err, "ensuring role binding")
   675  	}
   676  
   677  	err = ensureExecRBACResources(objMetaNamespaced, clock, broker)
   678  	return sa.Name, cleanUpFuncs, errors.Trace(err)
   679  }
   680  
   681  func ensureExecRBACResources(objMeta meta.ObjectMeta, clock jujuclock.Clock, broker ModelOperatorBroker) error {
   682  	objMeta.SetName(ExecRBACResourceName)
   683  
   684  	sa := &core.ServiceAccount{
   685  		ObjectMeta:                   objMeta,
   686  		AutomountServiceAccountToken: boolPtr(true),
   687  	}
   688  	_, err := broker.EnsureServiceAccount(sa)
   689  	if err != nil {
   690  		return errors.Annotatef(err, "ensuring service account %q", sa.GetName())
   691  	}
   692  
   693  	role := &rbac.Role{
   694  		ObjectMeta: objMeta,
   695  		Rules: []rbac.PolicyRule{
   696  			{
   697  				APIGroups: []string{""},
   698  				Resources: []string{"namespaces"},
   699  				Verbs: []string{
   700  					"get",
   701  					"list",
   702  				},
   703  				ResourceNames: []string{
   704  					objMeta.Namespace,
   705  				},
   706  			},
   707  			{
   708  				APIGroups: []string{""},
   709  				Resources: []string{"pods"},
   710  				Verbs: []string{
   711  					"get",
   712  					"list",
   713  				},
   714  			},
   715  			{
   716  				APIGroups: []string{""},
   717  				Resources: []string{"pods/exec"},
   718  				Verbs: []string{
   719  					"create",
   720  				},
   721  			},
   722  		},
   723  	}
   724  	_, err = broker.EnsureRole(role)
   725  	if err != nil {
   726  		return errors.Annotatef(err, "ensuring role %q", role.GetName())
   727  	}
   728  
   729  	roleBinding := &rbac.RoleBinding{
   730  		ObjectMeta: objMeta,
   731  		RoleRef: rbac.RoleRef{
   732  			APIGroup: "rbac.authorization.k8s.io",
   733  			Kind:     "Role",
   734  			Name:     role.Name,
   735  		},
   736  		Subjects: []rbac.Subject{
   737  			{
   738  				Kind:      "ServiceAccount",
   739  				Name:      sa.Name,
   740  				Namespace: sa.Namespace,
   741  			},
   742  		},
   743  	}
   744  
   745  	_, err = broker.EnsureRoleBinding(roleBinding)
   746  	if err != nil {
   747  		return errors.Annotatef(err, "ensuring role binding %q", roleBinding.Name)
   748  	}
   749  
   750  	_, err = proxy.EnsureSecretForServiceAccount(
   751  		sa.GetName(), objMeta, clock,
   752  		broker.Client().CoreV1().Secrets(objMeta.GetNamespace()),
   753  		broker.Client().CoreV1().ServiceAccounts(objMeta.GetNamespace()),
   754  	)
   755  	return errors.Trace(err)
   756  }
   757  
   758  func modelOperatorConfigMapAgentConfKey(operatorName string) string {
   759  	return operatorName + "-agent.conf"
   760  }