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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	jujuclock "github.com/juju/clock"
    18  	"github.com/juju/collections/set"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/names/v5"
    22  	"github.com/juju/version/v2"
    23  	"github.com/kr/pretty"
    24  	apps "k8s.io/api/apps/v1"
    25  	core "k8s.io/api/core/v1"
    26  	networkingv1 "k8s.io/api/networking/v1"
    27  	storagev1 "k8s.io/api/storage/v1"
    28  	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    29  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/api/resource"
    31  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	k8slabels "k8s.io/apimachinery/pkg/labels"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/util/intstr"
    36  	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
    37  	"k8s.io/client-go/dynamic"
    38  	"k8s.io/client-go/informers"
    39  	"k8s.io/client-go/kubernetes"
    40  	"k8s.io/client-go/rest"
    41  	"k8s.io/utils/pointer"
    42  
    43  	"github.com/juju/juju/caas"
    44  	k8sapplication "github.com/juju/juju/caas/kubernetes/provider/application"
    45  	"github.com/juju/juju/caas/kubernetes/provider/constants"
    46  	k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants"
    47  	"github.com/juju/juju/caas/kubernetes/provider/resources"
    48  	k8sspecs "github.com/juju/juju/caas/kubernetes/provider/specs"
    49  	k8sstorage "github.com/juju/juju/caas/kubernetes/provider/storage"
    50  	"github.com/juju/juju/caas/kubernetes/provider/utils"
    51  	k8swatcher "github.com/juju/juju/caas/kubernetes/provider/watcher"
    52  	"github.com/juju/juju/caas/specs"
    53  	"github.com/juju/juju/cloudconfig/podcfg"
    54  	k8sannotations "github.com/juju/juju/core/annotations"
    55  	"github.com/juju/juju/core/arch"
    56  	"github.com/juju/juju/core/assumes"
    57  	coreconfig "github.com/juju/juju/core/config"
    58  	"github.com/juju/juju/core/devices"
    59  	"github.com/juju/juju/core/paths"
    60  	coreresources "github.com/juju/juju/core/resources"
    61  	"github.com/juju/juju/core/status"
    62  	"github.com/juju/juju/core/watcher"
    63  	"github.com/juju/juju/docker"
    64  	"github.com/juju/juju/environs"
    65  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    66  	"github.com/juju/juju/environs/config"
    67  	envcontext "github.com/juju/juju/environs/context"
    68  	"github.com/juju/juju/storage"
    69  	jujuversion "github.com/juju/juju/version"
    70  )
    71  
    72  var logger = loggo.GetLogger("juju.kubernetes.provider")
    73  
    74  const (
    75  	// labelResourceLifeCycleKey defines the label key for lifecycle of the global resources.
    76  	labelResourceLifeCycleKey             = "juju-resource-lifecycle"
    77  	labelResourceLifeCycleValueModel      = "model"
    78  	labelResourceLifeCycleValuePersistent = "persistent"
    79  
    80  	gpuAffinityNodeSelectorKey = "gpu"
    81  
    82  	operatorInitContainerName = "juju-init"
    83  	operatorContainerName     = "juju-operator"
    84  
    85  	dataDirVolumeName = "juju-data-dir"
    86  
    87  	// InformerResyncPeriod is the default resync period set on IndexInformers
    88  	InformerResyncPeriod = time.Minute * 5
    89  
    90  	// A set of constants defining history limits for certain k8s deployment
    91  	// types.
    92  	// TODO We may want to make these configurable in the future.
    93  
    94  	// daemonsetRevisionHistoryLimit is the number of old history states to
    95  	// retain to allow rollbacks
    96  	daemonsetRevisionHistoryLimit int32 = 0
    97  	// deploymentRevisionHistoryLimit is the number of old ReplicaSets to retain
    98  	// to allow rollback
    99  	deploymentRevisionHistoryLimit int32 = 0
   100  	// statefulSetRevisionHistoryLimit is the maximum number of revisions that
   101  	// will be maintained in the StatefulSet's revision history
   102  	statefulSetRevisionHistoryLimit int32 = 0
   103  )
   104  
   105  type kubernetesClient struct {
   106  	clock jujuclock.Clock
   107  
   108  	// namespace is the k8s namespace to use when
   109  	// creating k8s resources.
   110  	namespace string
   111  
   112  	annotations k8sannotations.Annotation
   113  
   114  	lock                        sync.Mutex
   115  	envCfgUnlocked              *config.Config
   116  	k8sCfgUnlocked              *rest.Config
   117  	clientUnlocked              kubernetes.Interface
   118  	apiextensionsClientUnlocked apiextensionsclientset.Interface
   119  	dynamicClientUnlocked       dynamic.Interface
   120  
   121  	newClient     NewK8sClientFunc
   122  	newRestClient k8sspecs.NewK8sRestClientFunc
   123  
   124  	// modelUUID is the UUID of the model this client acts on.
   125  	modelUUID string
   126  
   127  	// newWatcher is the k8s watcher generator.
   128  	newWatcher        k8swatcher.NewK8sWatcherFunc
   129  	newStringsWatcher k8swatcher.NewK8sStringsWatcherFunc
   130  
   131  	// informerFactoryUnlocked informer factory setup for tracking this model
   132  	informerFactoryUnlocked informers.SharedInformerFactory
   133  
   134  	// isLegacyLabels describes if this client should use and implement legacy
   135  	// labels or new ones
   136  	isLegacyLabels bool
   137  
   138  	// randomPrefix generates an annotation for stateful sets.
   139  	randomPrefix utils.RandomPrefixFunc
   140  }
   141  
   142  // To regenerate the mocks for the kubernetes Client used by this broker,
   143  // run "go generate" from the package directory.
   144  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/k8sclient_mock.go k8s.io/client-go/kubernetes Interface
   145  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/context_mock.go context Context
   146  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/appv1_mock.go k8s.io/client-go/kubernetes/typed/apps/v1 AppsV1Interface,DeploymentInterface,StatefulSetInterface,DaemonSetInterface
   147  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/corev1_mock.go k8s.io/client-go/kubernetes/typed/core/v1 EventInterface,CoreV1Interface,NamespaceInterface,PodInterface,ServiceInterface,ConfigMapInterface,PersistentVolumeInterface,PersistentVolumeClaimInterface,SecretInterface,NodeInterface
   148  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/networkingv1beta1_mock.go -mock_names=IngressInterface=MockIngressV1Beta1Interface k8s.io/client-go/kubernetes/typed/networking/v1beta1 NetworkingV1beta1Interface,IngressInterface
   149  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/networkingv1_mock.go -mock_names=IngressInterface=MockIngressV1Interface k8s.io/client-go/kubernetes/typed/networking/v1 NetworkingV1Interface,IngressInterface,IngressClassInterface
   150  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/storagev1_mock.go k8s.io/client-go/kubernetes/typed/storage/v1 StorageV1Interface,StorageClassInterface
   151  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/rbacv1_mock.go k8s.io/client-go/kubernetes/typed/rbac/v1 RbacV1Interface,ClusterRoleBindingInterface,ClusterRoleInterface,RoleInterface,RoleBindingInterface
   152  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/apiextensionsv1beta1_mock.go -mock_names=CustomResourceDefinitionInterface=MockCustomResourceDefinitionV1Beta1Interface k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1 ApiextensionsV1beta1Interface,CustomResourceDefinitionInterface
   153  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/apiextensionsv1_mock.go -mock_names=CustomResourceDefinitionInterface=MockCustomResourceDefinitionV1Interface k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1 ApiextensionsV1Interface,CustomResourceDefinitionInterface
   154  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/apiextensionsclientset_mock.go -mock_names=Interface=MockApiExtensionsClientInterface k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset Interface
   155  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/discovery_mock.go k8s.io/client-go/discovery DiscoveryInterface
   156  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/dynamic_mock.go -mock_names=Interface=MockDynamicInterface k8s.io/client-go/dynamic Interface,ResourceInterface,NamespaceableResourceInterface
   157  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/admissionregistrationv1beta1_mock.go -mock_names=MutatingWebhookConfigurationInterface=MockMutatingWebhookConfigurationV1Beta1Interface,ValidatingWebhookConfigurationInterface=MockValidatingWebhookConfigurationV1Beta1Interface k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1  AdmissionregistrationV1beta1Interface,MutatingWebhookConfigurationInterface,ValidatingWebhookConfigurationInterface
   158  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/admissionregistrationv1_mock.go -mock_names=MutatingWebhookConfigurationInterface=MockMutatingWebhookConfigurationV1Interface,ValidatingWebhookConfigurationInterface=MockValidatingWebhookConfigurationV1Interface k8s.io/client-go/kubernetes/typed/admissionregistration/v1  AdmissionregistrationV1Interface,MutatingWebhookConfigurationInterface,ValidatingWebhookConfigurationInterface
   159  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/serviceaccountinformer_mock.go k8s.io/client-go/informers/core/v1 ServiceAccountInformer
   160  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/serviceaccountlister_mock.go k8s.io/client-go/listers/core/v1 ServiceAccountLister,ServiceAccountNamespaceLister
   161  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/sharedindexinformer_mock.go k8s.io/client-go/tools/cache SharedIndexInformer
   162  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/restclient_mock.go -mock_names=Interface=MockRestClientInterface k8s.io/client-go/rest Interface
   163  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/serviceaccount_mock.go k8s.io/client-go/kubernetes/typed/core/v1 ServiceAccountInterface
   164  
   165  // NewK8sClientFunc defines a function which returns a k8s client based on the supplied config.
   166  type NewK8sClientFunc func(c *rest.Config) (kubernetes.Interface, apiextensionsclientset.Interface, dynamic.Interface, error)
   167  
   168  // newK8sBroker returns a kubernetes client for the specified k8s cluster.
   169  func newK8sBroker(
   170  	controllerUUID string,
   171  	k8sRestConfig *rest.Config,
   172  	cfg *config.Config,
   173  	namespace string,
   174  	newClient NewK8sClientFunc,
   175  	newRestClient k8sspecs.NewK8sRestClientFunc,
   176  	newWatcher k8swatcher.NewK8sWatcherFunc,
   177  	newStringsWatcher k8swatcher.NewK8sStringsWatcherFunc,
   178  	randomPrefix utils.RandomPrefixFunc,
   179  	clock jujuclock.Clock,
   180  ) (*kubernetesClient, error) {
   181  	k8sClient, apiextensionsClient, dynamicClient, err := newClient(k8sRestConfig)
   182  	if err != nil {
   183  		return nil, errors.Trace(err)
   184  	}
   185  	newCfg, err := providerInstance.newConfig(cfg)
   186  	if err != nil {
   187  		return nil, errors.Trace(err)
   188  	}
   189  
   190  	modelUUID := newCfg.UUID()
   191  	if modelUUID == "" {
   192  		return nil, errors.NotValidf("modelUUID is required")
   193  	}
   194  
   195  	isLegacy := false
   196  	if namespace != "" {
   197  		isLegacy, err = utils.IsLegacyModelLabels(
   198  			namespace, newCfg.Config.Name(), k8sClient.CoreV1().Namespaces())
   199  		if err != nil {
   200  			return nil, errors.Trace(err)
   201  		}
   202  	}
   203  
   204  	client := &kubernetesClient{
   205  		clock:                       clock,
   206  		clientUnlocked:              k8sClient,
   207  		apiextensionsClientUnlocked: apiextensionsClient,
   208  		dynamicClientUnlocked:       dynamicClient,
   209  		envCfgUnlocked:              newCfg.Config,
   210  		k8sCfgUnlocked:              k8sRestConfig,
   211  		informerFactoryUnlocked: informers.NewSharedInformerFactoryWithOptions(
   212  			k8sClient,
   213  			InformerResyncPeriod,
   214  			informers.WithNamespace(namespace),
   215  		),
   216  		namespace:         namespace,
   217  		modelUUID:         modelUUID,
   218  		newWatcher:        newWatcher,
   219  		newStringsWatcher: newStringsWatcher,
   220  		newClient:         newClient,
   221  		newRestClient:     newRestClient,
   222  		randomPrefix:      randomPrefix,
   223  		annotations: k8sannotations.New(nil).
   224  			Add(utils.AnnotationModelUUIDKey(isLegacy), modelUUID),
   225  		isLegacyLabels: isLegacy,
   226  	}
   227  	if len(controllerUUID) > 0 {
   228  		client.annotations.Add(utils.AnnotationControllerUUIDKey(isLegacy), controllerUUID)
   229  	}
   230  	if namespace == "" {
   231  		return client, nil
   232  	}
   233  
   234  	ns, err := client.getNamespaceByName(namespace)
   235  	if errors.Is(err, errors.NotFound) {
   236  		return client, nil
   237  	} else if err != nil {
   238  		return nil, errors.Trace(err)
   239  	}
   240  
   241  	if !isK8sObjectOwnedByJuju(ns.ObjectMeta) {
   242  		return client, nil
   243  	}
   244  
   245  	if err := client.ensureNamespaceAnnotationForControllerUUID(ns, controllerUUID, isLegacy); err != nil {
   246  		return nil, errors.Trace(err)
   247  	}
   248  	return client, nil
   249  }
   250  
   251  func (k *kubernetesClient) ensureNamespaceAnnotationForControllerUUID(
   252  	ns *core.Namespace,
   253  	controllerUUID string,
   254  	isLegacy bool,
   255  ) error {
   256  	if len(controllerUUID) == 0 {
   257  		// controllerUUID could be empty in add-k8s without -c because there might be no controller yet.
   258  		return nil
   259  	}
   260  
   261  	annotationControllerUUIDKey := utils.AnnotationControllerUUIDKey(isLegacy)
   262  
   263  	if !isLegacy {
   264  		// Ignore the controller uuid since it is handled below for model migrations.
   265  		expected := k.annotations.Copy()
   266  		expected.Remove(annotationControllerUUIDKey)
   267  		if ns != nil && !k8sannotations.New(ns.Annotations).HasAll(expected) {
   268  			// This should never happen unless we changed annotations for a new juju version.
   269  			// But in this case, we should have already managed to fix it in upgrade steps.
   270  			return fmt.Errorf("annotations %v for namespace %q %w must include %v",
   271  				ns.Annotations, k.namespace, errors.NotValid, k.annotations)
   272  		}
   273  	}
   274  	if ns.Annotations[annotationControllerUUIDKey] == controllerUUID {
   275  		// No change needs to be done.
   276  		return nil
   277  	}
   278  	// The model was just migrated from a different controller.
   279  	logger.Debugf("model %q was migrated from controller %q, updating the controller annotation to %q", k.namespace,
   280  		ns.Annotations[annotationControllerUUIDKey], controllerUUID,
   281  	)
   282  	if err := k.ensureNamespaceAnnotations(ns); err != nil {
   283  		return errors.Trace(err)
   284  	}
   285  	_, err := k.client().CoreV1().Namespaces().Update(context.TODO(), ns, v1.UpdateOptions{})
   286  	return errors.Trace(err)
   287  }
   288  
   289  // GetAnnotations returns current namespace's annotations.
   290  func (k *kubernetesClient) GetAnnotations() k8sannotations.Annotation {
   291  	return k.annotations
   292  }
   293  
   294  var k8sversionNumberExtractor = regexp.MustCompile("[0-9]+")
   295  
   296  // Version returns cluster version information.
   297  func (k *kubernetesClient) Version() (ver *version.Number, err error) {
   298  	k8sver, err := k.client().Discovery().ServerVersion()
   299  	if err != nil {
   300  		return nil, errors.Trace(err)
   301  	}
   302  
   303  	clean := func(s string) string {
   304  		return k8sversionNumberExtractor.FindString(s)
   305  	}
   306  
   307  	ver = &version.Number{}
   308  	if ver.Major, err = strconv.Atoi(clean(k8sver.Major)); err != nil {
   309  		return nil, errors.Trace(err)
   310  	}
   311  	if ver.Minor, err = strconv.Atoi(clean(k8sver.Minor)); err != nil {
   312  		return nil, errors.Trace(err)
   313  	}
   314  	return ver, nil
   315  }
   316  
   317  // addAnnotations set an annotation to current namespace's annotations.
   318  func (k *kubernetesClient) addAnnotations(key, value string) k8sannotations.Annotation {
   319  	return k.annotations.Add(key, value)
   320  }
   321  
   322  func (k *kubernetesClient) client() kubernetes.Interface {
   323  	k.lock.Lock()
   324  	defer k.lock.Unlock()
   325  	client := k.clientUnlocked
   326  	return client
   327  }
   328  
   329  func (k *kubernetesClient) extendedClient() apiextensionsclientset.Interface {
   330  	k.lock.Lock()
   331  	defer k.lock.Unlock()
   332  	client := k.apiextensionsClientUnlocked
   333  	return client
   334  }
   335  
   336  func (k *kubernetesClient) dynamicClient() dynamic.Interface {
   337  	k.lock.Lock()
   338  	defer k.lock.Unlock()
   339  	client := k.dynamicClientUnlocked
   340  	return client
   341  }
   342  
   343  // Config returns environ config.
   344  func (k *kubernetesClient) Config() *config.Config {
   345  	k.lock.Lock()
   346  	defer k.lock.Unlock()
   347  	cfg := k.envCfgUnlocked
   348  	return cfg
   349  }
   350  
   351  func (k *kubernetesClient) k8sConfig() *rest.Config {
   352  	k.lock.Lock()
   353  	defer k.lock.Unlock()
   354  	return rest.CopyConfig(k.k8sCfgUnlocked)
   355  }
   356  
   357  // SetConfig is specified in the Environ interface.
   358  func (k *kubernetesClient) SetConfig(cfg *config.Config) error {
   359  	k.lock.Lock()
   360  	defer k.lock.Unlock()
   361  	newCfg, err := providerInstance.newConfig(cfg)
   362  	if err != nil {
   363  		return errors.Trace(err)
   364  	}
   365  	k.envCfgUnlocked = newCfg.Config
   366  	return nil
   367  }
   368  
   369  // SetCloudSpec is specified in the environs.Environ interface.
   370  func (k *kubernetesClient) SetCloudSpec(_ context.Context, spec environscloudspec.CloudSpec) error {
   371  	if k.namespace == "" {
   372  		return errNoNamespace
   373  	}
   374  	k.lock.Lock()
   375  	defer k.lock.Unlock()
   376  
   377  	k8sRestConfig, err := CloudSpecToK8sRestConfig(spec)
   378  	if err != nil {
   379  		return errors.Annotate(err, "cannot set cloud spec")
   380  	}
   381  
   382  	k.clientUnlocked, k.apiextensionsClientUnlocked, k.dynamicClientUnlocked, err = k.newClient(k8sRestConfig)
   383  	if err != nil {
   384  		return errors.Annotate(err, "cannot set cloud spec")
   385  	}
   386  	k.k8sCfgUnlocked = rest.CopyConfig(k8sRestConfig)
   387  
   388  	k.informerFactoryUnlocked = informers.NewSharedInformerFactoryWithOptions(
   389  		k.clientUnlocked,
   390  		InformerResyncPeriod,
   391  		informers.WithNamespace(k.namespace),
   392  	)
   393  	return nil
   394  }
   395  
   396  // PrepareForBootstrap prepares for bootstrapping a controller.
   397  func (k *kubernetesClient) PrepareForBootstrap(ctx environs.BootstrapContext, controllerName string) error {
   398  	alreadyExistErr := errors.NewAlreadyExists(nil,
   399  		fmt.Sprintf(`a controller called %q already exists on this k8s cluster.
   400  Please bootstrap again and choose a different controller name.`, controllerName),
   401  	)
   402  
   403  	k.namespace = DecideControllerNamespace(controllerName)
   404  
   405  	// ensure no existing namespace has the same name.
   406  	_, err := k.getNamespaceByName(k.namespace)
   407  	if err == nil {
   408  		return alreadyExistErr
   409  	}
   410  	if !errors.IsNotFound(err) {
   411  		return errors.Trace(err)
   412  	}
   413  	// Good, no existing namespace has the same name.
   414  	// Now, try to find if there is any existing controller running in this cluster.
   415  	// Note: we have to do this check before we are confident to support multi controllers running in same k8s cluster.
   416  
   417  	_, err = k.listNamespacesByAnnotations(k.annotations)
   418  	if err == nil {
   419  		return alreadyExistErr
   420  	}
   421  	if !errors.IsNotFound(err) {
   422  		return errors.Trace(err)
   423  	}
   424  	// All good, no existing controller found on the cluster.
   425  	// The namespace will be set to controller-name in newcontrollerStack.
   426  
   427  	// do validation on storage class.
   428  	_, err = k.validateOperatorStorage()
   429  	return errors.Trace(err)
   430  }
   431  
   432  // Create (environs.BootstrapEnviron) creates a new environ.
   433  // It must raise an error satisfying IsAlreadyExists if the
   434  // namespace is already used by another model.
   435  func (k *kubernetesClient) Create(envcontext.ProviderCallContext, environs.CreateParams) error {
   436  	return errors.Trace(k.createNamespace(k.namespace))
   437  }
   438  
   439  // EnsureImageRepoSecret ensures the image pull secret gets created.
   440  func (k *kubernetesClient) EnsureImageRepoSecret(imageRepo docker.ImageRepoDetails) error {
   441  	if !imageRepo.IsPrivate() {
   442  		return nil
   443  	}
   444  	logger.Debugf("creating secret for image repo %q", imageRepo.Repository)
   445  	secretData, err := imageRepo.SecretData()
   446  	if err != nil {
   447  		return errors.Trace(err)
   448  	}
   449  	_, err = k.ensureOCIImageSecret(
   450  		constants.CAASImageRepoSecretName,
   451  		utils.LabelsJuju, secretData,
   452  		k.annotations,
   453  	)
   454  	return errors.Trace(err)
   455  }
   456  
   457  // Bootstrap deploys controller with mongoDB together into k8s cluster.
   458  func (k *kubernetesClient) Bootstrap(
   459  	ctx environs.BootstrapContext,
   460  	callCtx envcontext.ProviderCallContext,
   461  	args environs.BootstrapParams,
   462  ) (*environs.BootstrapResult, error) {
   463  
   464  	if args.BootstrapSeries != "" {
   465  		return nil, errors.NotSupportedf("set series for bootstrapping to kubernetes")
   466  	}
   467  
   468  	storageClass, err := k.validateOperatorStorage()
   469  	if err != nil {
   470  		return nil, errors.Trace(err)
   471  	}
   472  
   473  	finalizer := func(ctx environs.BootstrapContext, pcfg *podcfg.ControllerPodConfig, opts environs.BootstrapDialOpts) (err error) {
   474  		podcfg.FinishControllerPodConfig(pcfg, k.Config(), args.ExtraAgentValuesForTesting)
   475  		if err = pcfg.VerifyConfig(); err != nil {
   476  			return errors.Trace(err)
   477  		}
   478  
   479  		logger.Debugf("controller pod config: \n%+v", pcfg)
   480  
   481  		// validate initial model name if we need to create it.
   482  		if initialModelName, has := pcfg.GetInitialModel(); has {
   483  			_, err := k.getNamespaceByName(initialModelName)
   484  			if err == nil {
   485  				return errors.NewAlreadyExists(nil,
   486  					fmt.Sprintf(`
   487  namespace %q already exists in the cluster,
   488  please choose a different initial model name then try again.`, initialModelName),
   489  				)
   490  			}
   491  			if !errors.IsNotFound(err) {
   492  				return errors.Trace(err)
   493  			}
   494  			// hosted model is all good.
   495  		}
   496  
   497  		// we use controller name to name controller namespace in bootstrap time.
   498  		setControllerNamespace := func(controllerName string, broker *kubernetesClient) error {
   499  			nsName := DecideControllerNamespace(controllerName)
   500  
   501  			_, err := broker.GetNamespace(nsName)
   502  			if errors.IsNotFound(err) {
   503  				// all good.
   504  				// ensure controller specific annotations.
   505  				_ = broker.addAnnotations(utils.AnnotationControllerIsControllerKey(k.IsLegacyLabels()), "true")
   506  				return nil
   507  			}
   508  			if err == nil {
   509  				// this should never happen because we avoid it in broker.PrepareForBootstrap before reaching here.
   510  				return errors.NotValidf("existing namespace %q found", broker.namespace)
   511  			}
   512  			return errors.Trace(err)
   513  		}
   514  
   515  		if err := setControllerNamespace(pcfg.ControllerName, k); err != nil {
   516  			return errors.Trace(err)
   517  		}
   518  
   519  		// create configmap, secret, volume, statefulset, etc resources for controller stack.
   520  		controllerStack, err := newcontrollerStack(ctx, k8sconstants.JujuControllerStackName, storageClass, k, pcfg)
   521  		if err != nil {
   522  			return errors.Trace(err)
   523  		}
   524  		return errors.Annotate(
   525  			controllerStack.Deploy(),
   526  			"creating controller stack",
   527  		)
   528  	}
   529  
   530  	podArch := arch.AMD64
   531  	if args.BootstrapConstraints.HasArch() {
   532  		podArch = *args.BootstrapConstraints.Arch
   533  	}
   534  	// TODO(wallyworld) - use actual series of controller pod image
   535  	return &environs.BootstrapResult{
   536  		Arch:                   podArch,
   537  		Base:                   jujuversion.DefaultSupportedLTSBase(),
   538  		CaasBootstrapFinalizer: finalizer,
   539  	}, nil
   540  }
   541  
   542  // DestroyController implements the Environ interface.
   543  func (k *kubernetesClient) DestroyController(ctx envcontext.ProviderCallContext, controllerUUID string) error {
   544  	// ensures all annnotations are set correctly, then we will accurately find the controller namespace to destroy it.
   545  	k.annotations.Merge(
   546  		k8sannotations.New(nil).
   547  			Add(utils.AnnotationControllerUUIDKey(k.IsLegacyLabels()), controllerUUID).
   548  			Add(utils.AnnotationControllerIsControllerKey(k.IsLegacyLabels()), "true"),
   549  	)
   550  	return k.Destroy(ctx)
   551  }
   552  
   553  // SharedInformerFactory returns the default k8s SharedInformerFactory used by
   554  // this broker.
   555  func (k *kubernetesClient) SharedInformerFactory() informers.SharedInformerFactory {
   556  	k.lock.Lock()
   557  	defer k.lock.Unlock()
   558  	return k.informerFactoryUnlocked
   559  }
   560  
   561  func (k *kubernetesClient) CurrentModel() string {
   562  	k.lock.Lock()
   563  	defer k.lock.Unlock()
   564  	return k.envCfgUnlocked.Name()
   565  }
   566  
   567  // Provider is part of the Broker interface.
   568  func (*kubernetesClient) Provider() caas.ContainerEnvironProvider {
   569  	return providerInstance
   570  }
   571  
   572  // Destroy is part of the Broker interface.
   573  func (k *kubernetesClient) Destroy(ctx envcontext.ProviderCallContext) (err error) {
   574  	defer func() {
   575  		if errors.Cause(err) == context.DeadlineExceeded {
   576  			logger.Warningf("destroy k8s model timeout")
   577  			return
   578  		}
   579  		if err != nil && k8serrors.ReasonForError(err) == v1.StatusReasonUnknown {
   580  			logger.Warningf("k8s cluster is not accessible: %v", err)
   581  			err = nil
   582  		}
   583  	}()
   584  
   585  	errChan := make(chan error, 1)
   586  	done := make(chan struct{})
   587  
   588  	destroyCtx, cancel := context.WithCancel(ctx)
   589  	defer cancel()
   590  
   591  	var wg sync.WaitGroup
   592  	wg.Add(1)
   593  	go k.deleteClusterScopeResourcesModelTeardown(destroyCtx, &wg, errChan)
   594  	wg.Add(1)
   595  	go k.deleteNamespaceModelTeardown(destroyCtx, &wg, errChan)
   596  
   597  	go func() {
   598  		wg.Wait()
   599  		close(done)
   600  	}()
   601  
   602  	for {
   603  		select {
   604  		case err = <-errChan:
   605  			if err != nil {
   606  				return errors.Trace(err)
   607  			}
   608  		case <-destroyCtx.Done():
   609  			return destroyCtx.Err()
   610  		case <-done:
   611  			return destroyCtx.Err()
   612  		}
   613  	}
   614  }
   615  
   616  // APIVersion returns the version info for the cluster.
   617  func (k *kubernetesClient) APIVersion() (string, error) {
   618  	ver, err := k.Version()
   619  	if err != nil {
   620  		return "", errors.Trace(err)
   621  	}
   622  	return ver.String(), nil
   623  }
   624  
   625  // getStorageClass returns a named storage class, first looking for
   626  // one which is qualified by the current namespace if it's available.
   627  func (k *kubernetesClient) getStorageClass(name string) (*storagev1.StorageClass, error) {
   628  	if k.namespace == "" {
   629  		return nil, errNoNamespace
   630  	}
   631  	storageClasses := k.client().StorageV1().StorageClasses()
   632  	qualifiedName := constants.QualifiedStorageClassName(k.namespace, name)
   633  	sc, err := storageClasses.Get(context.TODO(), qualifiedName, v1.GetOptions{})
   634  	if err == nil {
   635  		return sc, nil
   636  	}
   637  	if !k8serrors.IsNotFound(err) {
   638  		return nil, errors.Trace(err)
   639  	}
   640  	return storageClasses.Get(context.TODO(), name, v1.GetOptions{})
   641  }
   642  
   643  // GetService returns the service for the specified application.
   644  func (k *kubernetesClient) GetService(appName string, mode caas.DeploymentMode, includeClusterIP bool) (*caas.Service, error) {
   645  	if k.namespace == "" {
   646  		return nil, errNoNamespace
   647  	}
   648  	services := k.client().CoreV1().Services(k.namespace)
   649  	labels := utils.LabelsForApp(appName, k.IsLegacyLabels())
   650  	if mode == caas.ModeOperator {
   651  		labels = utils.LabelsForOperator(appName, OperatorAppTarget, k.IsLegacyLabels())
   652  	}
   653  	if !k.IsLegacyLabels() {
   654  		labels = utils.LabelsMerge(labels, utils.LabelsJuju)
   655  	}
   656  
   657  	servicesList, err := services.List(context.TODO(), v1.ListOptions{
   658  		LabelSelector: utils.LabelsToSelector(labels).String(),
   659  	})
   660  	if err != nil {
   661  		return nil, errors.Trace(err)
   662  	}
   663  	var (
   664  		result caas.Service
   665  		svc    *core.Service
   666  	)
   667  	// We may have the stateful set or deployment but service not done yet.
   668  	if len(servicesList.Items) > 0 {
   669  		for _, v := range servicesList.Items {
   670  			s := v
   671  			// Ignore any headless service for this app.
   672  			if !strings.HasSuffix(s.Name, "-endpoints") {
   673  				svc = &s
   674  				break
   675  			}
   676  		}
   677  		if svc != nil {
   678  			result.Id = string(svc.GetUID())
   679  			result.Addresses = utils.GetSvcAddresses(svc, includeClusterIP)
   680  		}
   681  	}
   682  
   683  	if mode == caas.ModeOperator {
   684  		appName = k.operatorName(appName)
   685  	}
   686  	deploymentName := k.deploymentName(appName, true)
   687  	statefulsets := k.client().AppsV1().StatefulSets(k.namespace)
   688  	ss, err := statefulsets.Get(context.TODO(), deploymentName, v1.GetOptions{})
   689  	if err != nil && !k8serrors.IsNotFound(err) {
   690  		return nil, errors.Trace(err)
   691  	}
   692  	if err == nil {
   693  		if ss.Spec.Replicas != nil {
   694  			scale := int(*ss.Spec.Replicas)
   695  			result.Scale = &scale
   696  		}
   697  		gen := ss.GetGeneration()
   698  		result.Generation = &gen
   699  		message, ssStatus, err := k.getStatefulSetStatus(ss)
   700  		if err != nil {
   701  			return nil, errors.Annotatef(err, "getting status for %s", ss.Name)
   702  		}
   703  		result.Status = status.StatusInfo{
   704  			Status:  ssStatus,
   705  			Message: message,
   706  		}
   707  		return &result, nil
   708  	}
   709  
   710  	deployments := k.client().AppsV1().Deployments(k.namespace)
   711  	deployment, err := deployments.Get(context.TODO(), deploymentName, v1.GetOptions{})
   712  	if err != nil && !k8serrors.IsNotFound(err) {
   713  		return nil, errors.Trace(err)
   714  	}
   715  	if err == nil {
   716  		if deployment.Spec.Replicas != nil {
   717  			scale := int(*deployment.Spec.Replicas)
   718  			result.Scale = &scale
   719  		}
   720  		gen := deployment.GetGeneration()
   721  		result.Generation = &gen
   722  		message, deployStatus, err := k.getDeploymentStatus(deployment)
   723  		if err != nil {
   724  			return nil, errors.Annotatef(err, "getting status for %s", ss.Name)
   725  		}
   726  		result.Status = status.StatusInfo{
   727  			Status:  deployStatus,
   728  			Message: message,
   729  		}
   730  		return &result, nil
   731  	}
   732  
   733  	daemonsets := k.client().AppsV1().DaemonSets(k.namespace)
   734  	ds, err := daemonsets.Get(context.TODO(), deploymentName, v1.GetOptions{})
   735  	if err != nil && !k8serrors.IsNotFound(err) {
   736  		return nil, errors.Trace(err)
   737  	}
   738  	if err == nil {
   739  		// The total number of nodes that should be running the daemon pod (including nodes correctly running the daemon pod).
   740  		scale := int(ds.Status.DesiredNumberScheduled)
   741  		result.Scale = &scale
   742  
   743  		gen := ds.GetGeneration()
   744  		result.Generation = &gen
   745  		message, dsStatus, err := k.getDaemonSetStatus(ds)
   746  		if err != nil {
   747  			return nil, errors.Annotatef(err, "getting status for %s", ss.Name)
   748  		}
   749  		result.Status = status.StatusInfo{
   750  			Status:  dsStatus,
   751  			Message: message,
   752  		}
   753  	}
   754  	return &result, nil
   755  }
   756  
   757  // DeleteService deletes the specified service with all related resources.
   758  func (k *kubernetesClient) DeleteService(appName string) (err error) {
   759  	logger.Debugf("deleting application %s", appName)
   760  
   761  	// We prefer deleting resources using labels to do bulk deletion.
   762  	// Deleting resources using deployment name has been deprecated.
   763  	// But we keep it for now because some old resources created by
   764  	// very old Juju probably do not have proper labels set.
   765  	deploymentName := k.deploymentName(appName, true)
   766  	if err := k.deleteService(deploymentName); err != nil {
   767  		return errors.Trace(err)
   768  	}
   769  	if err := k.deleteStatefulSet(deploymentName); err != nil {
   770  		return errors.Trace(err)
   771  	}
   772  	if err := k.deleteService(headlessServiceName(deploymentName)); err != nil {
   773  		return errors.Trace(err)
   774  	}
   775  	if err := k.deleteDeployment(deploymentName); err != nil {
   776  		return errors.Trace(err)
   777  	}
   778  
   779  	if err := k.deleteStatefulSets(appName); err != nil {
   780  		return errors.Trace(err)
   781  	}
   782  	if err := k.deleteDeployments(appName); err != nil {
   783  		return errors.Trace(err)
   784  	}
   785  	if err := k.deleteServices(appName); err != nil {
   786  		return errors.Trace(err)
   787  	}
   788  
   789  	if err := k.deleteSecrets(appName); err != nil {
   790  		return errors.Trace(err)
   791  	}
   792  	if err := k.deleteConfigMaps(appName); err != nil {
   793  		return errors.Trace(err)
   794  	}
   795  	if err := k.deleteAllServiceAccountResources(appName); err != nil {
   796  		return errors.Trace(err)
   797  	}
   798  	// Order matters: delete custom resources first then custom resource definitions.
   799  	if err := k.deleteCustomResourcesForApp(appName); err != nil {
   800  		return errors.Trace(err)
   801  	}
   802  	if err := k.deleteCustomResourceDefinitionsForApp(appName); err != nil {
   803  		return errors.Trace(err)
   804  	}
   805  
   806  	if err := k.deleteMutatingWebhookConfigurationsForApp(appName); err != nil {
   807  		return errors.Trace(err)
   808  	}
   809  	if err := k.deleteValidatingWebhookConfigurationsForApp(appName); err != nil {
   810  		return errors.Trace(err)
   811  	}
   812  
   813  	if err := k.deleteIngressResources(appName); err != nil {
   814  		return errors.Trace(err)
   815  	}
   816  
   817  	if err := k.deleteDaemonSets(appName); err != nil {
   818  		return errors.Trace(err)
   819  	}
   820  	return nil
   821  }
   822  
   823  const applyRawSpecTimeoutSeconds = 20
   824  
   825  func (k *kubernetesClient) applyRawK8sSpec(
   826  	appName, deploymentName string,
   827  	statusCallback caas.StatusCallbackFunc,
   828  	params *caas.ServiceParams,
   829  	numUnits int,
   830  	config coreconfig.ConfigAttributes,
   831  ) (err error) {
   832  	if k.namespace == "" {
   833  		return errNoNamespace
   834  	}
   835  
   836  	if params == nil || len(params.RawK8sSpec) == 0 {
   837  		return errors.Errorf("missing raw k8s spec")
   838  	}
   839  
   840  	if params.Deployment.DeploymentType == "" {
   841  		params.Deployment.DeploymentType = caas.DeploymentStateless
   842  		if len(params.Filesystems) > 0 {
   843  			params.Deployment.DeploymentType = caas.DeploymentStateful
   844  		}
   845  	}
   846  
   847  	// TODO(caas): support Constraints, FileSystems, Devices, InitContainer for actions, etc.
   848  	if err := params.Deployment.DeploymentType.Validate(); err != nil {
   849  		return errors.Trace(err)
   850  	}
   851  
   852  	labelGetter := func(isNamespaced bool) map[string]string {
   853  		labels := utils.SelectorLabelsForApp(appName, k.IsLegacyLabels())
   854  		if !isNamespaced {
   855  			labels = utils.LabelsMerge(
   856  				labels,
   857  				utils.LabelsForModel(k.CurrentModel(), k.IsLegacyLabels()),
   858  			)
   859  		}
   860  		return labels
   861  	}
   862  	annotations := utils.ResourceTagsToAnnotations(params.ResourceTags, k.IsLegacyLabels())
   863  
   864  	builder := k8sspecs.New(
   865  		deploymentName, k.namespace, params.Deployment, k.k8sConfig(),
   866  		labelGetter, annotations, k.newRestClient,
   867  	)
   868  	ctx, cancel := context.WithTimeout(context.Background(), applyRawSpecTimeoutSeconds*time.Second)
   869  	defer cancel()
   870  	return builder.Deploy(ctx, params.RawK8sSpec, true)
   871  }
   872  
   873  // EnsureService creates or updates a service for pods with the given params.
   874  func (k *kubernetesClient) EnsureService(
   875  	appName string,
   876  	statusCallback caas.StatusCallbackFunc,
   877  	params *caas.ServiceParams,
   878  	numUnits int,
   879  	config coreconfig.ConfigAttributes,
   880  ) (err error) {
   881  	defer func() {
   882  		if err != nil {
   883  			_ = statusCallback(appName, status.Error, err.Error(), nil)
   884  		}
   885  	}()
   886  
   887  	logger.Debugf("creating/updating application %s", appName)
   888  	deploymentName := k.deploymentName(appName, true)
   889  
   890  	if numUnits < 0 {
   891  		return errors.Errorf("number of units must be >= 0")
   892  	}
   893  	if numUnits == 0 {
   894  		return k.deleteAllPods(appName, deploymentName)
   895  	}
   896  	if params.PodSpec != nil && len(params.RawK8sSpec) > 0 {
   897  		// This should never happen.
   898  		return errors.NotValidf("both pod spec and raw k8s spec provided")
   899  	}
   900  
   901  	if params.PodSpec != nil {
   902  		if config == nil {
   903  			return errors.Errorf("config for k8s app %q cannot be nil", appName)
   904  		}
   905  		return k.ensureService(appName, deploymentName, statusCallback, params, numUnits, config)
   906  	}
   907  	if len(params.RawK8sSpec) > 0 {
   908  		return k.applyRawK8sSpec(appName, deploymentName, statusCallback, params, numUnits, config)
   909  	}
   910  	return nil
   911  }
   912  
   913  func (k *kubernetesClient) ensureService(
   914  	appName, deploymentName string,
   915  	statusCallback caas.StatusCallbackFunc,
   916  	params *caas.ServiceParams,
   917  	numUnits int,
   918  	config coreconfig.ConfigAttributes,
   919  ) (err error) {
   920  
   921  	if params == nil || params.PodSpec == nil {
   922  		return errors.Errorf("missing pod spec")
   923  	}
   924  
   925  	if err := params.Deployment.DeploymentType.Validate(); err != nil {
   926  		return errors.Trace(err)
   927  	}
   928  
   929  	var cleanups []func()
   930  	defer func() {
   931  		if err == nil {
   932  			return
   933  		}
   934  		for _, f := range cleanups {
   935  			f()
   936  		}
   937  	}()
   938  
   939  	workloadSpec, err := prepareWorkloadSpec(appName, deploymentName, params.PodSpec, params.ImageDetails)
   940  	if err != nil {
   941  		return errors.Annotatef(err, "parsing unit spec for %s", appName)
   942  	}
   943  
   944  	annotations := utils.ResourceTagsToAnnotations(params.ResourceTags, k.IsLegacyLabels())
   945  
   946  	// ensure services.
   947  	if len(workloadSpec.Services) > 0 {
   948  		servicesCleanUps, err := k.ensureServicesForApp(appName, deploymentName, annotations, workloadSpec.Services)
   949  		cleanups = append(cleanups, servicesCleanUps...)
   950  		if err != nil {
   951  			return errors.Annotate(err, "creating or updating services")
   952  		}
   953  	}
   954  
   955  	// ensure configmap.
   956  	if len(workloadSpec.ConfigMaps) > 0 {
   957  		cmsCleanUps, err := k.ensureConfigMaps(appName, annotations, workloadSpec.ConfigMaps)
   958  		cleanups = append(cleanups, cmsCleanUps...)
   959  		if err != nil {
   960  			return errors.Annotate(err, "creating or updating configmaps")
   961  		}
   962  	}
   963  
   964  	// ensure secrets.
   965  	if len(workloadSpec.Secrets) > 0 {
   966  		secretsCleanUps, err := k.ensureSecrets(appName, annotations, workloadSpec.Secrets)
   967  		cleanups = append(cleanups, secretsCleanUps...)
   968  		if err != nil {
   969  			return errors.Annotate(err, "creating or updating secrets")
   970  		}
   971  	}
   972  
   973  	// ensure custom resource definitions.
   974  	crds := workloadSpec.CustomResourceDefinitions
   975  	if len(crds) > 0 {
   976  		crdCleanUps, err := k.ensureCustomResourceDefinitions(appName, annotations, crds)
   977  		cleanups = append(cleanups, crdCleanUps...)
   978  		if err != nil {
   979  			return errors.Annotate(err, "creating or updating custom resource definitions")
   980  		}
   981  		logger.Debugf("created/updated custom resource definition for %q.", appName)
   982  
   983  	}
   984  	// ensure custom resources.
   985  	crs := workloadSpec.CustomResources
   986  	if len(crs) > 0 {
   987  		crCleanUps, err := k.ensureCustomResources(appName, annotations, crs)
   988  		cleanups = append(cleanups, crCleanUps...)
   989  		if err != nil {
   990  			return errors.Annotate(err, "creating or updating custom resources")
   991  		}
   992  		logger.Debugf("created/updated custom resources for %q.", appName)
   993  	}
   994  
   995  	// ensure mutating webhook configurations.
   996  	mutatingWebhookConfigurations := workloadSpec.MutatingWebhookConfigurations
   997  	if len(mutatingWebhookConfigurations) > 0 {
   998  		cfgCleanUps, err := k.ensureMutatingWebhookConfigurations(appName, annotations, mutatingWebhookConfigurations)
   999  		cleanups = append(cleanups, cfgCleanUps...)
  1000  		if err != nil {
  1001  			return errors.Annotate(err, "creating or updating mutating webhook configurations")
  1002  		}
  1003  		logger.Debugf("created/updated mutating webhook configurations for %q.", appName)
  1004  	}
  1005  	// ensure validating webhook configurations.
  1006  	validatingWebhookConfigurations := workloadSpec.ValidatingWebhookConfigurations
  1007  	if len(validatingWebhookConfigurations) > 0 {
  1008  		cfgCleanUps, err := k.ensureValidatingWebhookConfigurations(appName, annotations, validatingWebhookConfigurations)
  1009  		cleanups = append(cleanups, cfgCleanUps...)
  1010  		if err != nil {
  1011  			return errors.Annotate(err, "creating or updating validating webhook configurations")
  1012  		}
  1013  		logger.Debugf("created/updated validating webhook configurations for %q.", appName)
  1014  	}
  1015  
  1016  	// ensure ingress resources.
  1017  	ings := workloadSpec.IngressResources
  1018  	if len(ings) > 0 {
  1019  		ingCleanUps, err := k.ensureIngressResources(appName, annotations, workloadSpec.IngressResources)
  1020  		cleanups = append(cleanups, ingCleanUps...)
  1021  		if err != nil {
  1022  			return errors.Annotate(err, "creating or updating ingress resources")
  1023  		}
  1024  		logger.Debugf("created/updated ingress resources for %q.", appName)
  1025  	}
  1026  
  1027  	for _, sa := range workloadSpec.ServiceAccounts {
  1028  		saCleanups, err := k.ensureServiceAccountForApp(appName, annotations, sa)
  1029  		cleanups = append(cleanups, saCleanups...)
  1030  		if err != nil {
  1031  			return errors.Annotate(err, "creating or updating service account")
  1032  		}
  1033  	}
  1034  
  1035  	if len(params.Devices) > 0 {
  1036  		if err = k.configureDevices(workloadSpec, params.Devices); err != nil {
  1037  			return errors.Annotatef(err, "configuring devices for %s", appName)
  1038  		}
  1039  	}
  1040  	if err := k8sapplication.ApplyConstraints(
  1041  		&workloadSpec.Pod.PodSpec, appName, params.Constraints,
  1042  		func(pod *core.PodSpec, resourceName core.ResourceName, value string) error {
  1043  			if len(pod.Containers) == 0 {
  1044  				return nil
  1045  			}
  1046  			// Just the first container is enough for scheduling purposes.
  1047  			pod.Containers[0].Resources.Requests, err = k8sapplication.MergeConstraint(
  1048  				resourceName, value, pod.Containers[0].Resources.Requests,
  1049  			)
  1050  			if err != nil {
  1051  				return errors.Annotatef(err, "merging request constraint %s=%s", resourceName, value)
  1052  			}
  1053  			return nil
  1054  		},
  1055  	); err != nil {
  1056  		return errors.Trace(err)
  1057  	}
  1058  
  1059  	for _, c := range params.PodSpec.Containers {
  1060  		if c.ImageDetails.Password == "" {
  1061  			continue
  1062  		}
  1063  		imageSecretName := appSecretName(deploymentName, c.Name)
  1064  		if err := k.ensureOCIImageSecretForApp(
  1065  			imageSecretName, appName, &c.ImageDetails, annotations.Copy(),
  1066  		); err != nil {
  1067  			return errors.Annotatef(err, "creating secrets for container: %s", c.Name)
  1068  		}
  1069  		cleanups = append(cleanups, func() { _ = k.deleteSecret(imageSecretName, "") })
  1070  	}
  1071  	// Add a deployment controller or stateful set configured to create the specified number of units/pods.
  1072  	// Defensively check to see if a stateful set is already used.
  1073  	if params.Deployment.DeploymentType == "" {
  1074  		// TODO(caas): we should really change `params.Deployment` to be required.
  1075  		params.Deployment.DeploymentType = caas.DeploymentStateless
  1076  		if len(params.Filesystems) > 0 {
  1077  			params.Deployment.DeploymentType = caas.DeploymentStateful
  1078  		}
  1079  	}
  1080  	if params.Deployment.DeploymentType != caas.DeploymentStateful {
  1081  		// TODO(caas): make sure deployment type is immutable.
  1082  		// Either not found or params.Deployment.DeploymentType == existing resource type.
  1083  		_, err := k.getStatefulSet(deploymentName)
  1084  		if err != nil && !errors.IsNotFound(err) {
  1085  			return errors.Trace(err)
  1086  		}
  1087  		if err == nil {
  1088  			params.Deployment.DeploymentType = caas.DeploymentStateful
  1089  			logger.Debugf("no updated filesystems but already using stateful set for %q", appName)
  1090  		}
  1091  	}
  1092  
  1093  	if err = validateDeploymentType(params.Deployment.DeploymentType, params, workloadSpec.Service); err != nil {
  1094  		return errors.Trace(err)
  1095  	}
  1096  
  1097  	hasService := !params.PodSpec.OmitServiceFrontend && !params.Deployment.ServiceType.IsOmit()
  1098  	if hasService {
  1099  		var ports []core.ContainerPort
  1100  		for _, c := range workloadSpec.Pod.Containers {
  1101  			for _, p := range c.Ports {
  1102  				if p.ContainerPort == 0 {
  1103  					continue
  1104  				}
  1105  				ports = append(ports, p)
  1106  			}
  1107  		}
  1108  		if len(ports) == 0 {
  1109  			return errors.Errorf("ports are required for kubernetes service %q", appName)
  1110  		}
  1111  
  1112  		serviceAnnotations := annotations.Copy()
  1113  		// Merge any service annotations from the charm.
  1114  		if workloadSpec.Service != nil {
  1115  			serviceAnnotations.Merge(k8sannotations.New(workloadSpec.Service.Annotations))
  1116  		}
  1117  		// Merge any service annotations from the CLI.
  1118  		deployAnnotations, err := config.GetStringMap(serviceAnnotationsKey, nil)
  1119  		if err != nil {
  1120  			return errors.Annotatef(err, "unexpected annotations: %#v", config.Get(serviceAnnotationsKey, nil))
  1121  		}
  1122  		serviceAnnotations.Merge(k8sannotations.New(deployAnnotations))
  1123  
  1124  		config[serviceAnnotationsKey] = serviceAnnotations.ToMap()
  1125  		if err := k.configureService(appName, deploymentName, ports, params, config); err != nil {
  1126  			return errors.Annotatef(err, "creating or updating service for %v", appName)
  1127  		}
  1128  	}
  1129  
  1130  	numPods := int32(numUnits)
  1131  	workloadResourceAnnotations := annotations.Copy().
  1132  		// To solve https://bugs.launchpad.net/juju/+bug/1875481/comments/23 (`jujud caas-unit-init --upgrade`
  1133  		// does NOT work on containers are not using root as default USER),
  1134  		// CharmModifiedVersion is added for triggering rolling upgrade on workload pods to synchronise
  1135  		// charm files to workload pods via init container when charm was upgraded.
  1136  		// This approach was inspired from `kubectl rollout restart`.
  1137  		Add(utils.AnnotationCharmModifiedVersionKey(k.IsLegacyLabels()), strconv.Itoa(params.CharmModifiedVersion))
  1138  
  1139  	switch params.Deployment.DeploymentType {
  1140  	case caas.DeploymentStateful:
  1141  		if err := k.configureHeadlessService(appName, deploymentName, annotations.Copy()); err != nil {
  1142  			return errors.Annotate(err, "creating or updating headless service")
  1143  		}
  1144  		cleanups = append(cleanups, func() { _ = k.deleteService(headlessServiceName(deploymentName)) })
  1145  		if err := k.configureStatefulSet(appName, deploymentName, workloadResourceAnnotations.Copy(), workloadSpec, params.PodSpec.Containers, &numPods, params.Filesystems); err != nil {
  1146  			return errors.Annotate(err, "creating or updating StatefulSet")
  1147  		}
  1148  		cleanups = append(cleanups, func() { _ = k.deleteDeployment(appName) })
  1149  	case caas.DeploymentStateless:
  1150  		cleanUpDeployment, err := k.configureDeployment(appName, deploymentName, workloadResourceAnnotations.Copy(), workloadSpec, params.PodSpec.Containers, &numPods, params.Filesystems)
  1151  		cleanups = append(cleanups, cleanUpDeployment...)
  1152  		if err != nil {
  1153  			return errors.Annotate(err, "creating or updating Deployment")
  1154  		}
  1155  	case caas.DeploymentDaemon:
  1156  		cleanUpDaemonSet, err := k.configureDaemonSet(appName, deploymentName, workloadResourceAnnotations.Copy(), workloadSpec, params.PodSpec.Containers, params.Filesystems)
  1157  		cleanups = append(cleanups, cleanUpDaemonSet...)
  1158  		if err != nil {
  1159  			return errors.Annotate(err, "creating or updating DaemonSet")
  1160  		}
  1161  	default:
  1162  		// This should never happened because we have validated both in this method and in `charm.v6`.
  1163  		return errors.NotSupportedf("deployment type %q", params.Deployment.DeploymentType)
  1164  	}
  1165  	return nil
  1166  }
  1167  
  1168  func validateDeploymentType(deploymentType caas.DeploymentType, params *caas.ServiceParams, svcSpec *specs.ServiceSpec) error {
  1169  	if svcSpec == nil {
  1170  		return nil
  1171  	}
  1172  	if deploymentType != caas.DeploymentStateful {
  1173  		if svcSpec.ScalePolicy != "" {
  1174  			return errors.NewNotValid(nil, fmt.Sprintf("ScalePolicy is only supported for %s applications", caas.DeploymentStateful))
  1175  		}
  1176  	}
  1177  	return nil
  1178  }
  1179  
  1180  func (k *kubernetesClient) deleteAllPods(appName, deploymentName string) error {
  1181  	if k.namespace == "" {
  1182  		return errNoNamespace
  1183  	}
  1184  	zero := int32(0)
  1185  	statefulsets := k.client().AppsV1().StatefulSets(k.namespace)
  1186  	statefulSet, err := statefulsets.Get(context.TODO(), deploymentName, v1.GetOptions{})
  1187  	if err != nil && !k8serrors.IsNotFound(err) {
  1188  		return errors.Trace(err)
  1189  	}
  1190  	if err == nil {
  1191  		statefulSet.Spec.Replicas = &zero
  1192  		_, err = statefulsets.Update(context.TODO(), statefulSet, v1.UpdateOptions{})
  1193  		return errors.Trace(err)
  1194  	}
  1195  
  1196  	deployments := k.client().AppsV1().Deployments(k.namespace)
  1197  	deployment, err := deployments.Get(context.TODO(), deploymentName, v1.GetOptions{})
  1198  	if k8serrors.IsNotFound(err) {
  1199  		return nil
  1200  	}
  1201  	if err != nil {
  1202  		return errors.Trace(err)
  1203  	}
  1204  	deployment.Spec.Replicas = &zero
  1205  	_, err = deployments.Update(context.TODO(), deployment, v1.UpdateOptions{})
  1206  	return errors.Trace(err)
  1207  }
  1208  
  1209  type annotationGetter interface {
  1210  	GetAnnotations() map[string]string
  1211  }
  1212  
  1213  // This random snippet will be included to the pvc name so that if the same app
  1214  // is deleted and redeployed again, the pvc retains a unique name.
  1215  // Only generate it once, and record it on the workload resource annotations .
  1216  func (k *kubernetesClient) getStorageUniqPrefix(getMeta func() (annotationGetter, error)) (string, error) {
  1217  	r, err := getMeta()
  1218  	if err == nil {
  1219  		if uniqID := r.GetAnnotations()[utils.AnnotationKeyApplicationUUID(k.IsLegacyLabels())]; uniqID != "" {
  1220  			return uniqID, nil
  1221  		}
  1222  	} else if !errors.IsNotFound(err) {
  1223  		return "", errors.Trace(err)
  1224  	}
  1225  	return k.randomPrefix()
  1226  }
  1227  
  1228  func (k *kubernetesClient) configureDevices(unitSpec *workloadSpec, devices []devices.KubernetesDeviceParams) error {
  1229  	for i := range unitSpec.Pod.Containers {
  1230  		resources := unitSpec.Pod.Containers[i].Resources
  1231  		for _, dev := range devices {
  1232  			err := mergeDeviceConstraints(dev, &resources)
  1233  			if err != nil {
  1234  				return errors.Annotatef(err, "merging device constraint %+v to %#v", dev, resources)
  1235  			}
  1236  		}
  1237  		unitSpec.Pod.Containers[i].Resources = resources
  1238  	}
  1239  	nodeLabel, err := getNodeSelectorFromDeviceConstraints(devices)
  1240  	if err != nil {
  1241  		return err
  1242  	}
  1243  	if nodeLabel != "" {
  1244  		nodeSelector := buildNodeSelector(nodeLabel)
  1245  		if unitSpec.Pod.NodeSelector != nil {
  1246  			for k, v := range nodeSelector {
  1247  				unitSpec.Pod.NodeSelector[k] = v
  1248  			}
  1249  		} else {
  1250  			unitSpec.Pod.NodeSelector = nodeSelector
  1251  		}
  1252  	}
  1253  	return nil
  1254  }
  1255  
  1256  type configMapNameFunc func(fileSetName string) string
  1257  
  1258  func (k *kubernetesClient) configurePodFiles(
  1259  	appName string,
  1260  	annotations map[string]string,
  1261  	workloadSpec *workloadSpec,
  1262  	containers []specs.ContainerSpec,
  1263  	cfgMapName configMapNameFunc,
  1264  ) error {
  1265  	for _, container := range containers {
  1266  		for _, fileSet := range container.VolumeConfig {
  1267  			vol, err := k.fileSetToVolume(appName, annotations, workloadSpec, fileSet, cfgMapName)
  1268  			if err != nil {
  1269  				return errors.Trace(err)
  1270  			}
  1271  			if err = k8sstorage.PushUniqueVolume(&workloadSpec.Pod.PodSpec, vol, false); err != nil {
  1272  				return errors.Trace(err)
  1273  			}
  1274  			if err := configVolumeMount(
  1275  				container, workloadSpec,
  1276  				core.VolumeMount{
  1277  					// TODO(caas): add more config fields support(SubPath, ReadOnly, etc).
  1278  					Name:      vol.Name,
  1279  					MountPath: fileSet.MountPath,
  1280  				},
  1281  			); err != nil {
  1282  				return errors.Trace(err)
  1283  			}
  1284  		}
  1285  	}
  1286  	return nil
  1287  }
  1288  
  1289  func configVolumeMount(container specs.ContainerSpec, workloadSpec *workloadSpec, volMount core.VolumeMount) error {
  1290  	if container.Init {
  1291  		for i, c := range workloadSpec.Pod.InitContainers {
  1292  			if c.Name == container.Name {
  1293  				workloadSpec.Pod.InitContainers[i].VolumeMounts = append(workloadSpec.Pod.InitContainers[i].VolumeMounts, volMount)
  1294  				return nil
  1295  			}
  1296  		}
  1297  		return errors.Annotatef(errors.NotFoundf("init container %q", container.Name), "configuring volume mount %q", volMount.Name)
  1298  	}
  1299  	for i, c := range workloadSpec.Pod.Containers {
  1300  		if c.Name == container.Name {
  1301  			workloadSpec.Pod.Containers[i].VolumeMounts = append(workloadSpec.Pod.Containers[i].VolumeMounts, volMount)
  1302  			return nil
  1303  		}
  1304  	}
  1305  	return errors.Annotatef(errors.NotFoundf("container %q", container.Name), "configuring volume mount %q", volMount.Name)
  1306  }
  1307  
  1308  func (k *kubernetesClient) configureStorage(
  1309  	appName string, legacy bool, uniquePrefix string,
  1310  	filesystems []storage.KubernetesFilesystemParams,
  1311  	podSpec *core.PodSpec,
  1312  	handlePVC func(core.PersistentVolumeClaim, string, bool) error,
  1313  ) error {
  1314  	pvcNameGetter := func(i int, storageName string) string {
  1315  		s := fmt.Sprintf("%s-%s", storageName, uniquePrefix)
  1316  		if legacy {
  1317  			s = fmt.Sprintf("juju-%s-%d", storageName, i)
  1318  		}
  1319  		return s
  1320  	}
  1321  	fsNames := set.NewStrings()
  1322  	for i, fs := range filesystems {
  1323  		if fsNames.Contains(fs.StorageName) {
  1324  			return errors.NotValidf("duplicated storage name %q for %q", fs.StorageName, appName)
  1325  		}
  1326  		fsNames.Add(fs.StorageName)
  1327  
  1328  		readOnly := false
  1329  		if fs.Attachment != nil {
  1330  			readOnly = fs.Attachment.ReadOnly
  1331  		}
  1332  
  1333  		vol, pvc, err := k.filesystemToVolumeInfo(i, fs, pvcNameGetter)
  1334  		if err != nil {
  1335  			return errors.Trace(err)
  1336  		}
  1337  		mountPath := k8sstorage.GetMountPathForFilesystem(i, appName, fs)
  1338  		if vol != nil {
  1339  			logger.Debugf("using volume for %s filesystem %s: %s", appName, fs.StorageName, pretty.Sprint(*vol))
  1340  			if err = k8sstorage.PushUniqueVolume(podSpec, *vol, false); err != nil {
  1341  				return errors.Trace(err)
  1342  			}
  1343  			podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, core.VolumeMount{
  1344  				Name:      vol.Name,
  1345  				MountPath: mountPath,
  1346  			})
  1347  		}
  1348  		if pvc != nil && handlePVC != nil {
  1349  			logger.Debugf("using persistent volume claim for %s filesystem %s: %s", appName, fs.StorageName, pretty.Sprint(*pvc))
  1350  			if err = handlePVC(*pvc, mountPath, readOnly); err != nil {
  1351  				return errors.Trace(err)
  1352  			}
  1353  		}
  1354  	}
  1355  	return nil
  1356  }
  1357  
  1358  func ensureJujuInitContainer(podSpec *core.PodSpec, operatorImagePath string) error {
  1359  	initContainer, vol, volMounts, err := getJujuInitContainerAndStorageInfo(operatorImagePath)
  1360  	if err != nil {
  1361  		return errors.Trace(err)
  1362  	}
  1363  
  1364  	replaceOrUpdateInitContainer := func() {
  1365  		for i, v := range podSpec.InitContainers {
  1366  			if v.Name == initContainer.Name {
  1367  				podSpec.InitContainers[i] = initContainer
  1368  				return
  1369  			}
  1370  		}
  1371  		podSpec.InitContainers = append(podSpec.InitContainers, initContainer)
  1372  	}
  1373  	replaceOrUpdateInitContainer()
  1374  
  1375  	if err = k8sstorage.PushUniqueVolume(podSpec, vol, true); err != nil {
  1376  		return errors.Trace(err)
  1377  	}
  1378  
  1379  	for i := range podSpec.Containers {
  1380  		container := &podSpec.Containers[i]
  1381  		for _, volMount := range volMounts {
  1382  			k8sstorage.PushUniqueVolumeMount(container, volMount)
  1383  		}
  1384  	}
  1385  	return nil
  1386  }
  1387  
  1388  func getJujuInitContainerAndStorageInfo(operatorImagePath string) (container core.Container, vol core.Volume, volMounts []core.VolumeMount, err error) {
  1389  	dataDir := paths.DataDir(paths.OSUnixLike)
  1390  	jujuExec := paths.JujuExec(paths.OSUnixLike)
  1391  	jujudCmd := `
  1392  initCmd=$($JUJU_TOOLS_DIR/jujud help commands | grep caas-unit-init)
  1393  if test -n "$initCmd"; then
  1394  exec $JUJU_TOOLS_DIR/jujud caas-unit-init --debug --wait;
  1395  else
  1396  exit 0
  1397  fi`[1:]
  1398  	container = core.Container{
  1399  		Name:            caas.InitContainerName,
  1400  		Image:           operatorImagePath,
  1401  		ImagePullPolicy: core.PullIfNotPresent,
  1402  		VolumeMounts: []core.VolumeMount{{
  1403  			Name:      dataDirVolumeName,
  1404  			MountPath: dataDir,
  1405  		}},
  1406  		WorkingDir: dataDir,
  1407  		Command: []string{
  1408  			"/bin/sh",
  1409  		},
  1410  		Args: []string{
  1411  			"-c",
  1412  			fmt.Sprintf(
  1413  				caas.JujudStartUpSh,
  1414  				dataDir,
  1415  				"tools",
  1416  				jujudCmd,
  1417  			),
  1418  		},
  1419  	}
  1420  	vol = core.Volume{
  1421  		Name: dataDirVolumeName,
  1422  		VolumeSource: core.VolumeSource{
  1423  			EmptyDir: &core.EmptyDirVolumeSource{},
  1424  		},
  1425  	}
  1426  	volMounts = []core.VolumeMount{
  1427  		{Name: dataDirVolumeName, MountPath: dataDir},
  1428  		{Name: dataDirVolumeName, MountPath: jujuExec, SubPath: "tools/jujud"},
  1429  	}
  1430  	return container, vol, volMounts, nil
  1431  }
  1432  
  1433  func podAnnotations(annotations k8sannotations.Annotation) k8sannotations.Annotation {
  1434  	// Add standard security annotations.
  1435  	return annotations.
  1436  		Add("apparmor.security.beta.kubernetes.io/pod", "runtime/default").
  1437  		Add("seccomp.security.beta.kubernetes.io/pod", "docker/default")
  1438  }
  1439  
  1440  // https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/#daemonset-update-strategy
  1441  func updateStrategyForDaemonSet(strategy specs.UpdateStrategy) (o apps.DaemonSetUpdateStrategy, err error) {
  1442  	strategyType := apps.DaemonSetUpdateStrategyType(strategy.Type)
  1443  
  1444  	o = apps.DaemonSetUpdateStrategy{Type: strategyType}
  1445  	switch strategyType {
  1446  	case apps.OnDeleteDaemonSetStrategyType:
  1447  		if strategy.RollingUpdate != nil {
  1448  			return o, errors.NewNotValid(nil, fmt.Sprintf("rolling update spec is not supported for %q", strategyType))
  1449  		}
  1450  	case apps.RollingUpdateDaemonSetStrategyType:
  1451  		if strategy.RollingUpdate != nil {
  1452  			if strategy.RollingUpdate.Partition != nil || strategy.RollingUpdate.MaxSurge != nil {
  1453  				return o, errors.NotValidf("rolling update spec for daemonset")
  1454  			}
  1455  			if strategy.RollingUpdate.MaxUnavailable == nil {
  1456  				return o, errors.NewNotValid(nil, "rolling update spec maxUnavailable is missing")
  1457  			}
  1458  			o.RollingUpdate = &apps.RollingUpdateDaemonSet{
  1459  				MaxUnavailable: k8sspecs.IntOrStringToK8s(*strategy.RollingUpdate.MaxUnavailable),
  1460  			}
  1461  		}
  1462  	default:
  1463  		return o, errors.NotValidf("strategy type %q for daemonset", strategyType)
  1464  	}
  1465  	return o, nil
  1466  }
  1467  
  1468  func (k *kubernetesClient) configureDaemonSet(
  1469  	appName, deploymentName string,
  1470  	annotations k8sannotations.Annotation,
  1471  	workloadSpec *workloadSpec,
  1472  	containers []specs.ContainerSpec,
  1473  	filesystems []storage.KubernetesFilesystemParams,
  1474  ) (cleanUps []func(), err error) {
  1475  	logger.Debugf("creating/updating daemon set for %s", appName)
  1476  
  1477  	// Add the specified file to the pod spec.
  1478  	cfgName := func(fileSetName string) string {
  1479  		return applicationConfigMapName(deploymentName, fileSetName)
  1480  	}
  1481  	if err := k.configurePodFiles(appName, annotations, workloadSpec, containers, cfgName); err != nil {
  1482  		return cleanUps, errors.Trace(err)
  1483  	}
  1484  
  1485  	storageUniqueID, err := k.getStorageUniqPrefix(func() (annotationGetter, error) {
  1486  		return k.getDaemonSet(deploymentName)
  1487  	})
  1488  	if err != nil {
  1489  		return cleanUps, errors.Trace(err)
  1490  	}
  1491  
  1492  	selectorLabels := utils.SelectorLabelsForApp(appName, k.IsLegacyLabels())
  1493  	daemonSet := &apps.DaemonSet{
  1494  		ObjectMeta: v1.ObjectMeta{
  1495  			Name:   deploymentName,
  1496  			Labels: utils.LabelsForApp(appName, k.IsLegacyLabels()),
  1497  			Annotations: k8sannotations.New(nil).
  1498  				Merge(annotations).
  1499  				Add(utils.AnnotationKeyApplicationUUID(k.IsLegacyLabels()), storageUniqueID).ToMap(),
  1500  		},
  1501  		Spec: apps.DaemonSetSpec{
  1502  			// TODO(caas): MinReadySeconds support.
  1503  			Selector: &v1.LabelSelector{
  1504  				MatchLabels: selectorLabels,
  1505  			},
  1506  			RevisionHistoryLimit: pointer.Int32Ptr(daemonsetRevisionHistoryLimit),
  1507  			Template: core.PodTemplateSpec{
  1508  				ObjectMeta: v1.ObjectMeta{
  1509  					GenerateName: deploymentName + "-",
  1510  					Labels:       utils.LabelsMerge(workloadSpec.Pod.Labels, selectorLabels),
  1511  					Annotations:  podAnnotations(k8sannotations.New(workloadSpec.Pod.Annotations).Merge(annotations).Copy()).ToMap(),
  1512  				},
  1513  				Spec: workloadSpec.Pod.PodSpec,
  1514  			},
  1515  		},
  1516  	}
  1517  	if workloadSpec.Service != nil && workloadSpec.Service.UpdateStrategy != nil {
  1518  		if daemonSet.Spec.UpdateStrategy, err = updateStrategyForDaemonSet(*workloadSpec.Service.UpdateStrategy); err != nil {
  1519  			return cleanUps, errors.Trace(err)
  1520  		}
  1521  	}
  1522  
  1523  	handlePVC := func(pvc core.PersistentVolumeClaim, mountPath string, readOnly bool) error {
  1524  		cs, err := k.configurePVCForStatelessResource(pvc, mountPath, readOnly, &daemonSet.Spec.Template.Spec)
  1525  		cleanUps = append(cleanUps, cs...)
  1526  		return errors.Trace(err)
  1527  	}
  1528  	// Storage support for daemonset is new.
  1529  	legacy := false
  1530  	if err := k.configureStorage(appName, legacy, storageUniqueID, filesystems, &daemonSet.Spec.Template.Spec, handlePVC); err != nil {
  1531  		return cleanUps, errors.Trace(err)
  1532  	}
  1533  
  1534  	cU, err := k.ensureDaemonSet(daemonSet)
  1535  	cleanUps = append(cleanUps, cU)
  1536  	return cleanUps, errors.Trace(err)
  1537  }
  1538  
  1539  // https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
  1540  func updateStrategyForDeployment(strategy specs.UpdateStrategy) (o apps.DeploymentStrategy, err error) {
  1541  	strategyType := apps.DeploymentStrategyType(strategy.Type)
  1542  
  1543  	o = apps.DeploymentStrategy{Type: strategyType}
  1544  	switch strategyType {
  1545  	case apps.RecreateDeploymentStrategyType:
  1546  		if strategy.RollingUpdate != nil {
  1547  			return o, errors.NewNotValid(nil, fmt.Sprintf("rolling update spec is not supported for %q", strategyType))
  1548  		}
  1549  	case apps.RollingUpdateDeploymentStrategyType:
  1550  		if strategy.RollingUpdate != nil {
  1551  			if strategy.RollingUpdate.Partition != nil {
  1552  				return o, errors.NotValidf("rolling update spec for deployment")
  1553  			}
  1554  			if strategy.RollingUpdate.MaxSurge == nil && strategy.RollingUpdate.MaxUnavailable == nil {
  1555  				return o, errors.NewNotValid(nil, "empty rolling update spec")
  1556  			}
  1557  			o.RollingUpdate = &apps.RollingUpdateDeployment{}
  1558  			if strategy.RollingUpdate.MaxSurge != nil {
  1559  				o.RollingUpdate.MaxSurge = k8sspecs.IntOrStringToK8s(*strategy.RollingUpdate.MaxSurge)
  1560  			}
  1561  			if strategy.RollingUpdate.MaxUnavailable != nil {
  1562  				o.RollingUpdate.MaxUnavailable = k8sspecs.IntOrStringToK8s(*strategy.RollingUpdate.MaxUnavailable)
  1563  			}
  1564  		}
  1565  	default:
  1566  		return o, errors.NotValidf("strategy type %q for deployment", strategyType)
  1567  	}
  1568  	return o, nil
  1569  }
  1570  
  1571  func (k *kubernetesClient) configureDeployment(
  1572  	appName, deploymentName string,
  1573  	annotations k8sannotations.Annotation,
  1574  	workloadSpec *workloadSpec,
  1575  	containers []specs.ContainerSpec,
  1576  	replicas *int32,
  1577  	filesystems []storage.KubernetesFilesystemParams,
  1578  ) (cleanUps []func(), err error) {
  1579  	logger.Debugf("creating/updating deployment for %s", appName)
  1580  
  1581  	// Add the specified file to the pod spec.
  1582  	cfgName := func(fileSetName string) string {
  1583  		return applicationConfigMapName(deploymentName, fileSetName)
  1584  	}
  1585  	if err := k.configurePodFiles(appName, annotations, workloadSpec, containers, cfgName); err != nil {
  1586  		return cleanUps, errors.Trace(err)
  1587  	}
  1588  
  1589  	storageUniqueID, err := k.getStorageUniqPrefix(func() (annotationGetter, error) {
  1590  		return k.getDeployment(deploymentName)
  1591  	})
  1592  	if err != nil {
  1593  		return cleanUps, errors.Trace(err)
  1594  	}
  1595  
  1596  	selectorLabels := utils.SelectorLabelsForApp(appName, k.IsLegacyLabels())
  1597  	deployment := &apps.Deployment{
  1598  		ObjectMeta: v1.ObjectMeta{
  1599  			Name:   deploymentName,
  1600  			Labels: utils.LabelsForApp(appName, k.IsLegacyLabels()),
  1601  			Annotations: k8sannotations.New(nil).
  1602  				Merge(annotations).
  1603  				Add(utils.AnnotationKeyApplicationUUID(k.IsLegacyLabels()), storageUniqueID).ToMap(),
  1604  		},
  1605  		Spec: apps.DeploymentSpec{
  1606  			// TODO(caas): MinReadySeconds, ProgressDeadlineSeconds support.
  1607  			Replicas:             replicas,
  1608  			RevisionHistoryLimit: pointer.Int32Ptr(deploymentRevisionHistoryLimit),
  1609  			Selector: &v1.LabelSelector{
  1610  				MatchLabels: selectorLabels,
  1611  			},
  1612  			Template: core.PodTemplateSpec{
  1613  				ObjectMeta: v1.ObjectMeta{
  1614  					GenerateName: deploymentName + "-",
  1615  					Labels:       utils.LabelsMerge(workloadSpec.Pod.Labels, selectorLabels),
  1616  					Annotations:  podAnnotations(k8sannotations.New(workloadSpec.Pod.Annotations).Merge(annotations).Copy()).ToMap(),
  1617  				},
  1618  				Spec: workloadSpec.Pod.PodSpec,
  1619  			},
  1620  		},
  1621  	}
  1622  	if workloadSpec.Service != nil && workloadSpec.Service.UpdateStrategy != nil {
  1623  		if deployment.Spec.Strategy, err = updateStrategyForDeployment(*workloadSpec.Service.UpdateStrategy); err != nil {
  1624  			return cleanUps, errors.Trace(err)
  1625  		}
  1626  	}
  1627  	handlePVC := func(pvc core.PersistentVolumeClaim, mountPath string, readOnly bool) error {
  1628  		cs, err := k.configurePVCForStatelessResource(pvc, mountPath, readOnly, &deployment.Spec.Template.Spec)
  1629  		cleanUps = append(cleanUps, cs...)
  1630  		return errors.Trace(err)
  1631  	}
  1632  	// Storage support for deployment is new.
  1633  	legacy := false
  1634  	if err := k.configureStorage(appName, legacy, storageUniqueID, filesystems, &deployment.Spec.Template.Spec, handlePVC); err != nil {
  1635  		return cleanUps, errors.Trace(err)
  1636  	}
  1637  	if err = k.ensureDeployment(deployment); err != nil {
  1638  		return cleanUps, errors.Trace(err)
  1639  	}
  1640  	cleanUps = append(cleanUps, func() { _ = k.deleteDeployment(appName) })
  1641  	return cleanUps, nil
  1642  }
  1643  
  1644  func (k *kubernetesClient) configurePVCForStatelessResource(
  1645  	spec core.PersistentVolumeClaim, mountPath string, readOnly bool, podSpec *core.PodSpec,
  1646  ) (cleanUps []func(), err error) {
  1647  	pvc, pvcCleanUp, err := k.ensurePVC(&spec)
  1648  	cleanUps = append(cleanUps, pvcCleanUp)
  1649  	if err != nil {
  1650  		return cleanUps, errors.Annotatef(err, "ensuring PVC %q", spec.GetName())
  1651  	}
  1652  	vol := core.Volume{
  1653  		Name: pvc.GetName(),
  1654  		VolumeSource: core.VolumeSource{
  1655  			PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{
  1656  				ClaimName: pvc.GetName(),
  1657  				ReadOnly:  readOnly,
  1658  			},
  1659  		},
  1660  	}
  1661  	if err = k8sstorage.PushUniqueVolume(podSpec, vol, false); err != nil {
  1662  		return cleanUps, errors.Trace(err)
  1663  	}
  1664  	podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, core.VolumeMount{
  1665  		Name:      vol.Name,
  1666  		MountPath: mountPath,
  1667  	})
  1668  	return cleanUps, nil
  1669  }
  1670  
  1671  func (k *kubernetesClient) ensureDeployment(spec *apps.Deployment) error {
  1672  	if k.namespace == "" {
  1673  		return errNoNamespace
  1674  	}
  1675  	deployments := k.client().AppsV1().Deployments(k.namespace)
  1676  	_, err := k.createDeployment(spec)
  1677  	if err == nil || !errors.IsAlreadyExists(err) {
  1678  		return errors.Annotatef(err, "ensuring deployment %q", spec.GetName())
  1679  	}
  1680  	existing, err := k.getDeployment(spec.GetName())
  1681  	if err != nil {
  1682  		return errors.Trace(err)
  1683  	}
  1684  	existing.SetAnnotations(spec.GetAnnotations())
  1685  	existing.Spec = spec.Spec
  1686  	_, err = deployments.Update(context.TODO(), existing, v1.UpdateOptions{})
  1687  	if err != nil {
  1688  		return errors.Annotatef(err, "ensuring deployment %q", spec.GetName())
  1689  	}
  1690  	return errors.Trace(err)
  1691  }
  1692  
  1693  func (k *kubernetesClient) createDeployment(spec *apps.Deployment) (*apps.Deployment, error) {
  1694  	out, err := k.client().AppsV1().Deployments(k.namespace).Create(context.TODO(), spec, v1.CreateOptions{})
  1695  	if k8serrors.IsAlreadyExists(err) {
  1696  		return nil, errors.AlreadyExistsf("deployment %q", spec.GetName())
  1697  	}
  1698  	if k8serrors.IsInvalid(err) {
  1699  		return nil, errors.NotValidf("deployment %q", spec.GetName())
  1700  	}
  1701  	return out, errors.Trace(err)
  1702  }
  1703  
  1704  func (k *kubernetesClient) getDeployment(name string) (*apps.Deployment, error) {
  1705  	if k.namespace == "" {
  1706  		return nil, errNoNamespace
  1707  	}
  1708  	out, err := k.client().AppsV1().Deployments(k.namespace).Get(context.TODO(), name, v1.GetOptions{})
  1709  	if k8serrors.IsNotFound(err) {
  1710  		return nil, errors.NotFoundf("deployment %q", name)
  1711  	}
  1712  	return out, errors.Trace(err)
  1713  }
  1714  
  1715  func (k *kubernetesClient) deleteDeployment(name string) error {
  1716  	if k.namespace == "" {
  1717  		return errNoNamespace
  1718  	}
  1719  	err := k.client().AppsV1().Deployments(k.namespace).Delete(context.TODO(), name, v1.DeleteOptions{
  1720  		PropagationPolicy: constants.DefaultPropagationPolicy(),
  1721  	})
  1722  	if k8serrors.IsNotFound(err) {
  1723  		return nil
  1724  	}
  1725  	return errors.Trace(err)
  1726  }
  1727  
  1728  func (k *kubernetesClient) deleteDeployments(appName string) error {
  1729  	err := k.client().AppsV1().Deployments(k.namespace).DeleteCollection(context.TODO(), v1.DeleteOptions{
  1730  		PropagationPolicy: constants.DefaultPropagationPolicy(),
  1731  	}, v1.ListOptions{
  1732  		LabelSelector: utils.LabelsToSelector(
  1733  			utils.LabelsForApp(appName, k.IsLegacyLabels())).String(),
  1734  	})
  1735  	if k8serrors.IsNotFound(err) {
  1736  		return nil
  1737  	}
  1738  	return errors.Trace(err)
  1739  }
  1740  
  1741  func getPodManagementPolicy(svc *specs.ServiceSpec) (out apps.PodManagementPolicyType) {
  1742  	// Default to "Parallel".
  1743  	out = apps.ParallelPodManagement
  1744  	if svc == nil || svc.ScalePolicy == "" {
  1745  		return out
  1746  	}
  1747  
  1748  	switch svc.ScalePolicy {
  1749  	case specs.SerialScale:
  1750  		return apps.OrderedReadyPodManagement
  1751  	case specs.ParallelScale:
  1752  		return apps.ParallelPodManagement
  1753  		// no need to consider other cases because we have done validation in podspec parsing stage.
  1754  	}
  1755  	return out
  1756  }
  1757  
  1758  func (k *kubernetesClient) deleteVolumeClaims(appName string, p *core.Pod) ([]string, error) {
  1759  	if k.namespace == "" {
  1760  		return nil, errNoNamespace
  1761  	}
  1762  	volumesByName := make(map[string]core.Volume)
  1763  	for _, pv := range p.Spec.Volumes {
  1764  		volumesByName[pv.Name] = pv
  1765  	}
  1766  
  1767  	var deletedClaimVolumes []string
  1768  	for _, volMount := range p.Spec.Containers[0].VolumeMounts {
  1769  		vol, ok := volumesByName[volMount.Name]
  1770  		if !ok {
  1771  			logger.Warningf("volume for volume mount %q not found", volMount.Name)
  1772  			continue
  1773  		}
  1774  		if vol.PersistentVolumeClaim == nil {
  1775  			// Ignore volumes which are not Juju managed filesystems.
  1776  			continue
  1777  		}
  1778  		pvClaims := k.client().CoreV1().PersistentVolumeClaims(k.namespace)
  1779  		logger.Infof("deleting operator PVC %s for application %s due to call to kubernetesClient.deleteVolumeClaims", vol.PersistentVolumeClaim.ClaimName, appName)
  1780  		err := pvClaims.Delete(context.TODO(), vol.PersistentVolumeClaim.ClaimName, v1.DeleteOptions{
  1781  			PropagationPolicy: constants.DefaultPropagationPolicy(),
  1782  		})
  1783  		if err != nil && !k8serrors.IsNotFound(err) {
  1784  			return nil, errors.Annotatef(err, "deleting persistent volume claim %v for %v",
  1785  				vol.PersistentVolumeClaim.ClaimName, p.Name)
  1786  		}
  1787  		deletedClaimVolumes = append(deletedClaimVolumes, vol.Name)
  1788  	}
  1789  	return deletedClaimVolumes, nil
  1790  }
  1791  
  1792  // CaasServiceToK8s translates a caas service type to a k8s one.
  1793  func CaasServiceToK8s(in caas.ServiceType) (core.ServiceType, error) {
  1794  	serviceType := defaultServiceType
  1795  	if in != "" {
  1796  		switch in {
  1797  		case caas.ServiceCluster:
  1798  			serviceType = core.ServiceTypeClusterIP
  1799  		case caas.ServiceLoadBalancer:
  1800  			serviceType = core.ServiceTypeLoadBalancer
  1801  		case caas.ServiceExternal:
  1802  			serviceType = core.ServiceTypeExternalName
  1803  		case caas.ServiceOmit:
  1804  			logger.Debugf("no service to be created because service type is %q", in)
  1805  			return "", nil
  1806  		default:
  1807  			return "", errors.NotSupportedf("service type %q", in)
  1808  		}
  1809  	}
  1810  	return serviceType, nil
  1811  }
  1812  
  1813  func (k *kubernetesClient) configureService(
  1814  	appName, deploymentName string,
  1815  	containerPorts []core.ContainerPort,
  1816  	params *caas.ServiceParams,
  1817  	config coreconfig.ConfigAttributes,
  1818  ) error {
  1819  	logger.Debugf("creating/updating service for %s", appName)
  1820  
  1821  	var ports []core.ServicePort
  1822  	for i, cp := range containerPorts {
  1823  		// We normally expect a single container port for most use cases.
  1824  		// We allow the user to specify what first service port should be,
  1825  		// otherwise it just defaults to the container port.
  1826  		// TODO(caas) - consider allowing all service ports to be specified
  1827  		var targetPort intstr.IntOrString
  1828  		if i == 0 {
  1829  			targetPort = intstr.FromInt(config.GetInt(serviceTargetPortConfigKey, int(cp.ContainerPort)))
  1830  		}
  1831  		ports = append(ports, core.ServicePort{
  1832  			Name:       cp.Name,
  1833  			Protocol:   cp.Protocol,
  1834  			Port:       cp.ContainerPort,
  1835  			TargetPort: targetPort,
  1836  		})
  1837  	}
  1838  
  1839  	serviceType := caas.ServiceType(config.GetString(ServiceTypeConfigKey, string(params.Deployment.ServiceType)))
  1840  	k8sServiceType, err := CaasServiceToK8s(serviceType)
  1841  	if err != nil {
  1842  		return errors.Trace(err)
  1843  	}
  1844  	annotations, err := config.GetStringMap(serviceAnnotationsKey, nil)
  1845  	if err != nil {
  1846  		return errors.Annotatef(err, "unexpected annotations: %#v", config.Get(serviceAnnotationsKey, nil))
  1847  	}
  1848  	service := &core.Service{
  1849  		ObjectMeta: v1.ObjectMeta{
  1850  			Name:        deploymentName,
  1851  			Labels:      utils.LabelsForApp(appName, k.IsLegacyLabels()),
  1852  			Annotations: annotations,
  1853  		},
  1854  		Spec: core.ServiceSpec{
  1855  			Selector:                 utils.SelectorLabelsForApp(appName, k.IsLegacyLabels()),
  1856  			Type:                     k8sServiceType,
  1857  			Ports:                    ports,
  1858  			ExternalIPs:              config.Get(serviceExternalIPsConfigKey, []string(nil)).([]string),
  1859  			LoadBalancerIP:           config.GetString(serviceLoadBalancerIPKey, ""),
  1860  			LoadBalancerSourceRanges: config.Get(serviceLoadBalancerSourceRangesKey, []string(nil)).([]string),
  1861  			ExternalName:             config.GetString(serviceExternalNameKey, ""),
  1862  		},
  1863  	}
  1864  	_, err = k.ensureK8sService(service)
  1865  	return err
  1866  }
  1867  
  1868  func (k *kubernetesClient) configureHeadlessService(
  1869  	appName, deploymentName string, annotations k8sannotations.Annotation,
  1870  ) error {
  1871  	logger.Debugf("creating/updating headless service for %s", appName)
  1872  	service := &core.Service{
  1873  		ObjectMeta: v1.ObjectMeta{
  1874  			Name:   headlessServiceName(deploymentName),
  1875  			Labels: utils.LabelsForApp(appName, k.IsLegacyLabels()),
  1876  			Annotations: k8sannotations.New(nil).
  1877  				Merge(annotations).
  1878  				Add("service.alpha.kubernetes.io/tolerate-unready-endpoints", "true").ToMap(),
  1879  		},
  1880  		Spec: core.ServiceSpec{
  1881  			Selector:                 utils.SelectorLabelsForApp(appName, k.IsLegacyLabels()),
  1882  			Type:                     core.ServiceTypeClusterIP,
  1883  			ClusterIP:                "None",
  1884  			PublishNotReadyAddresses: true,
  1885  		},
  1886  	}
  1887  	_, err := k.ensureK8sService(service)
  1888  	return err
  1889  }
  1890  
  1891  func (k *kubernetesClient) findDefaultIngressClassResource() (*string, error) {
  1892  	ics, err := k.listIngressClasses(nil)
  1893  	if err != nil {
  1894  		return nil, errors.Annotate(err, "finding the default ingress class")
  1895  	}
  1896  	for _, ic := range ics {
  1897  		if k8sannotations.New(ic.GetAnnotations()).Has("ingressclass.kubernetes.io/is-default-class", "true") {
  1898  			return &ic.Name, nil
  1899  		}
  1900  	}
  1901  	return nil, errors.NotFoundf("default ingress class")
  1902  }
  1903  
  1904  // ExposeService sets up external access to the specified application.
  1905  func (k *kubernetesClient) ExposeService(appName string, resourceTags map[string]string, config coreconfig.ConfigAttributes) error {
  1906  	if k.namespace == "" {
  1907  		return errNoNamespace
  1908  	}
  1909  	logger.Debugf("creating/updating ingress resource for %s", appName)
  1910  
  1911  	host := config.GetString(caas.JujuExternalHostNameKey, "")
  1912  	if host == "" {
  1913  		return errors.Errorf("external hostname required")
  1914  	}
  1915  
  1916  	ingressSSLRedirect := config.GetBool(ingressSSLRedirectKey, defaultIngressSSLRedirect)
  1917  	ingressSSLPassthrough := config.GetBool(ingressSSLPassthroughKey, defaultIngressSSLPassthrough)
  1918  	ingressAllowHTTP := config.GetBool(ingressAllowHTTPKey, defaultIngressAllowHTTPKey)
  1919  	httpPath := config.GetString(caas.JujuApplicationPath, caas.JujuDefaultApplicationPath)
  1920  	if httpPath == "$appname" {
  1921  		httpPath = appName
  1922  	}
  1923  	if !strings.HasPrefix(httpPath, "/") {
  1924  		httpPath = "/" + httpPath
  1925  	}
  1926  
  1927  	deploymentName := k.deploymentName(appName, true)
  1928  	svc, err := k.client().CoreV1().Services(k.namespace).Get(context.TODO(), deploymentName, v1.GetOptions{})
  1929  	if err != nil {
  1930  		return errors.Trace(err)
  1931  	}
  1932  	if len(svc.Spec.Ports) == 0 {
  1933  		return errors.Errorf("cannot create ingress rule for service %q without a port", svc.Name)
  1934  	}
  1935  	spec := &networkingv1.Ingress{
  1936  		ObjectMeta: v1.ObjectMeta{
  1937  			Name:   deploymentName,
  1938  			Labels: k8slabels.Merge(resourceTags, k.getIngressLabels(appName)),
  1939  			Annotations: map[string]string{
  1940  				"ingress.kubernetes.io/rewrite-target":  "",
  1941  				"ingress.kubernetes.io/ssl-redirect":    strconv.FormatBool(ingressSSLRedirect),
  1942  				"kubernetes.io/ingress.allow-http":      strconv.FormatBool(ingressAllowHTTP),
  1943  				"ingress.kubernetes.io/ssl-passthrough": strconv.FormatBool(ingressSSLPassthrough),
  1944  			},
  1945  		},
  1946  		Spec: networkingv1.IngressSpec{},
  1947  	}
  1948  
  1949  	ingressClass := config.GetString(ingressClassKey, defaultIngressClass)
  1950  	if ingressClass == defaultIngressClass {
  1951  		if spec.Spec.IngressClassName, err = k.findDefaultIngressClassResource(); err != nil && !errors.IsNotFound(err) {
  1952  			return errors.Trace(err)
  1953  		}
  1954  	}
  1955  	pathType := networkingv1.PathTypeImplementationSpecific
  1956  	if spec.Spec.IngressClassName == nil {
  1957  		spec.Annotations["kubernetes.io/ingress.class"] = ingressClass
  1958  		pathType = networkingv1.PathTypePrefix
  1959  	}
  1960  	spec.Spec.Rules = append(spec.Spec.Rules,
  1961  		networkingv1.IngressRule{
  1962  			Host: host,
  1963  			IngressRuleValue: networkingv1.IngressRuleValue{
  1964  				HTTP: &networkingv1.HTTPIngressRuleValue{
  1965  					Paths: []networkingv1.HTTPIngressPath{
  1966  						{
  1967  							Path:     httpPath,
  1968  							PathType: &pathType,
  1969  							Backend: networkingv1.IngressBackend{
  1970  								Service: &networkingv1.IngressServiceBackend{
  1971  									Name: svc.Name,
  1972  									Port: networkingv1.ServiceBackendPort{
  1973  										Number: int32(svc.Spec.Ports[0].TargetPort.IntValue()),
  1974  									},
  1975  								},
  1976  							},
  1977  						},
  1978  					},
  1979  				},
  1980  			},
  1981  		},
  1982  	)
  1983  
  1984  	// TODO(caas): refactor juju expose to solve potential conflict with ingress definition in podspec.
  1985  	// https://bugs.launchpad.net/juju/+bug/1854123
  1986  	_, err = k.ensureIngressV1(appName, spec, true)
  1987  	return errors.Trace(err)
  1988  }
  1989  
  1990  // UnexposeService removes external access to the specified service.
  1991  func (k *kubernetesClient) UnexposeService(appName string) error {
  1992  	logger.Debugf("deleting ingress resource for %s", appName)
  1993  	deploymentName := k.deploymentName(appName, true)
  1994  	return errors.Trace(k.deleteIngress(deploymentName, ""))
  1995  }
  1996  
  1997  func (k *kubernetesClient) applicationSelector(appName string, mode caas.DeploymentMode) string {
  1998  	if mode == caas.ModeOperator {
  1999  		return operatorSelector(appName, k.IsLegacyLabels())
  2000  	}
  2001  	return utils.LabelsToSelector(
  2002  		utils.SelectorLabelsForApp(appName, k.IsLegacyLabels())).String()
  2003  }
  2004  
  2005  // AnnotateUnit annotates the specified pod (name or uid) with a unit tag.
  2006  func (k *kubernetesClient) AnnotateUnit(appName string, mode caas.DeploymentMode, podName string, unit names.UnitTag) error {
  2007  	if k.namespace == "" {
  2008  		return errNoNamespace
  2009  	}
  2010  	pods := k.client().CoreV1().Pods(k.namespace)
  2011  
  2012  	pod, err := pods.Get(context.TODO(), podName, v1.GetOptions{})
  2013  	if err != nil {
  2014  		if !k8serrors.IsNotFound(err) {
  2015  			return errors.Trace(err)
  2016  		}
  2017  		pods, err := pods.List(context.TODO(), v1.ListOptions{
  2018  			LabelSelector: k.applicationSelector(appName, mode),
  2019  		})
  2020  		// TODO(caas): remove getting pod by Id (a bit expensive) once we started to store podName in cloudContainer doc.
  2021  		if err != nil {
  2022  			return errors.Trace(err)
  2023  		}
  2024  		for _, v := range pods.Items {
  2025  			if string(v.GetUID()) == podName {
  2026  				p := v
  2027  				pod = &p
  2028  				break
  2029  			}
  2030  		}
  2031  	}
  2032  	if pod == nil {
  2033  		return errors.NotFoundf("pod %q", podName)
  2034  	}
  2035  
  2036  	unitID := unit.Id()
  2037  	if pod.Annotations != nil && pod.Annotations[utils.AnnotationUnitKey(k.IsLegacyLabels())] == unitID {
  2038  		return nil
  2039  	}
  2040  
  2041  	patch := struct {
  2042  		ObjectMeta struct {
  2043  			Annotations map[string]string `json:"annotations"`
  2044  		} `json:"metadata"`
  2045  	}{}
  2046  	patch.ObjectMeta.Annotations = map[string]string{
  2047  		utils.AnnotationUnitKey(k.IsLegacyLabels()): unitID,
  2048  	}
  2049  	jsonPatch, err := json.Marshal(patch)
  2050  	if err != nil {
  2051  		return errors.Trace(err)
  2052  	}
  2053  
  2054  	_, err = pods.Patch(context.TODO(), pod.Name, types.MergePatchType, jsonPatch, v1.PatchOptions{})
  2055  	if k8serrors.IsNotFound(err) {
  2056  		return errors.NotFoundf("pod %q", podName)
  2057  	}
  2058  	return errors.Trace(err)
  2059  }
  2060  
  2061  // WatchUnits returns a watcher which notifies when there
  2062  // are changes to units of the specified application.
  2063  func (k *kubernetesClient) WatchUnits(appName string, mode caas.DeploymentMode) (watcher.NotifyWatcher, error) {
  2064  	selector := k.applicationSelector(appName, mode)
  2065  	logger.Debugf("selecting units %q to watch", selector)
  2066  	factory := informers.NewSharedInformerFactoryWithOptions(k.client(), 0,
  2067  		informers.WithNamespace(k.namespace),
  2068  		informers.WithTweakListOptions(func(o *v1.ListOptions) {
  2069  			o.LabelSelector = selector
  2070  		}),
  2071  	)
  2072  	return k.newWatcher(factory.Core().V1().Pods().Informer(), appName, k.clock)
  2073  }
  2074  
  2075  // WatchContainerStart returns a watcher which is notified when a container matching containerName regexp
  2076  // is starting/restarting. Each string represents the provider id for the unit the container belongs to.
  2077  // If containerName regexp matches empty string, then the first workload container
  2078  // is used.
  2079  func (k *kubernetesClient) WatchContainerStart(appName string, containerName string) (watcher.StringsWatcher, error) {
  2080  	if k.namespace == "" {
  2081  		return nil, errNoNamespace
  2082  	}
  2083  	pods := k.client().CoreV1().Pods(k.namespace)
  2084  	selector := k.applicationSelector(appName, caas.ModeWorkload)
  2085  	logger.Debugf("selecting units %q to watch", selector)
  2086  	factory := informers.NewSharedInformerFactoryWithOptions(k.client(), 0,
  2087  		informers.WithNamespace(k.namespace),
  2088  		informers.WithTweakListOptions(func(o *v1.ListOptions) {
  2089  			o.LabelSelector = selector
  2090  		}),
  2091  	)
  2092  
  2093  	podsList, err := pods.List(context.TODO(), v1.ListOptions{
  2094  		LabelSelector: selector,
  2095  	})
  2096  	if err != nil {
  2097  		return nil, errors.Trace(err)
  2098  	}
  2099  
  2100  	containerNameRegex, err := regexp.Compile("^" + containerName + "$")
  2101  	if err != nil {
  2102  		return nil, errors.Trace(err)
  2103  	}
  2104  
  2105  	running := func(pod *core.Pod) set.Strings {
  2106  		if _, ok := pod.Annotations[utils.AnnotationUnitKey(k.IsLegacyLabels())]; !ok {
  2107  			// Ignore pods that aren't annotated as a unit yet.
  2108  			return set.Strings{}
  2109  		}
  2110  		running := set.Strings{}
  2111  		for _, cs := range pod.Status.InitContainerStatuses {
  2112  			if containerNameRegex.MatchString(cs.Name) {
  2113  				if cs.State.Running != nil {
  2114  					running.Add(cs.Name)
  2115  				}
  2116  			}
  2117  		}
  2118  		for i, cs := range pod.Status.ContainerStatuses {
  2119  			useDefault := i == 0 && containerNameRegex.MatchString("")
  2120  			if containerNameRegex.MatchString(cs.Name) || useDefault {
  2121  				if cs.State.Running != nil {
  2122  					running.Add(cs.Name)
  2123  				}
  2124  			}
  2125  		}
  2126  		return running
  2127  	}
  2128  
  2129  	podInitState := map[string]set.Strings{}
  2130  	var initialEvents []string
  2131  	for _, pod := range podsList.Items {
  2132  		if containers := running(&pod); !containers.IsEmpty() {
  2133  			podInitState[string(pod.GetUID())] = containers
  2134  			initialEvents = append(initialEvents, providerID(&pod))
  2135  		}
  2136  	}
  2137  
  2138  	filterEvent := func(evt k8swatcher.WatchEvent, obj interface{}) (string, bool) {
  2139  		pod, ok := obj.(*core.Pod)
  2140  		if !ok {
  2141  			return "", false
  2142  		}
  2143  		key := string(pod.GetUID())
  2144  		if evt == k8swatcher.WatchEventDelete {
  2145  			delete(podInitState, key)
  2146  			return "", false
  2147  		}
  2148  		if containers := running(pod); !containers.IsEmpty() {
  2149  			if last, ok := podInitState[key]; ok {
  2150  				podInitState[key] = containers
  2151  				if !containers.Difference(last).IsEmpty() {
  2152  					return providerID(pod), true
  2153  				}
  2154  			} else {
  2155  				podInitState[key] = containers
  2156  				return providerID(pod), true
  2157  			}
  2158  		} else {
  2159  			delete(podInitState, key)
  2160  		}
  2161  		return "", false
  2162  	}
  2163  
  2164  	return k.newStringsWatcher(factory.Core().V1().Pods().Informer(),
  2165  		appName, k.clock, initialEvents, filterEvent)
  2166  }
  2167  
  2168  // WatchService returns a watcher which notifies when there
  2169  // are changes to the deployment of the specified application.
  2170  func (k *kubernetesClient) WatchService(appName string, mode caas.DeploymentMode) (watcher.NotifyWatcher, error) {
  2171  	if k.namespace == "" {
  2172  		return nil, errNoNamespace
  2173  	}
  2174  	// Application may be a statefulset or deployment. It may not have
  2175  	// been set up when the watcher is started so we don't know which it
  2176  	// is ahead of time. So use a multi-watcher to cover both cases.
  2177  	factory := informers.NewSharedInformerFactoryWithOptions(k.client(), 0,
  2178  		informers.WithNamespace(k.namespace),
  2179  		informers.WithTweakListOptions(func(o *v1.ListOptions) {
  2180  			o.LabelSelector = k.applicationSelector(appName, mode)
  2181  		}),
  2182  	)
  2183  
  2184  	w1, err := k.newWatcher(factory.Apps().V1().StatefulSets().Informer(), appName, k.clock)
  2185  	if err != nil {
  2186  		return nil, errors.Trace(err)
  2187  	}
  2188  	w2, err := k.newWatcher(factory.Apps().V1().Deployments().Informer(), appName, k.clock)
  2189  	if err != nil {
  2190  		return nil, errors.Trace(err)
  2191  	}
  2192  	w3, err := k.newWatcher(factory.Core().V1().Services().Informer(), appName, k.clock)
  2193  	if err != nil {
  2194  		return nil, errors.Trace(err)
  2195  	}
  2196  
  2197  	return watcher.NewMultiNotifyWatcher(w1, w2, w3), nil
  2198  }
  2199  
  2200  // CheckCloudCredentials verifies the the cloud credentials provided to the
  2201  // broker are functioning.
  2202  func (k *kubernetesClient) CheckCloudCredentials() error {
  2203  	if _, err := k.Namespaces(); err != nil {
  2204  		// If this call could not be made with provided credential, we
  2205  		// know that the credential is invalid.
  2206  		return errors.Trace(err)
  2207  	}
  2208  	return nil
  2209  }
  2210  
  2211  // Units returns all units and any associated filesystems of the specified application.
  2212  // Filesystems are mounted via volumes bound to the unit.
  2213  func (k *kubernetesClient) Units(appName string, mode caas.DeploymentMode) ([]caas.Unit, error) {
  2214  	if k.namespace == "" {
  2215  		return nil, errNoNamespace
  2216  	}
  2217  	pods := k.client().CoreV1().Pods(k.namespace)
  2218  	podsList, err := pods.List(context.TODO(), v1.ListOptions{
  2219  		LabelSelector: k.applicationSelector(appName, mode),
  2220  	})
  2221  	if err != nil {
  2222  		return nil, errors.Trace(err)
  2223  	}
  2224  
  2225  	var units []caas.Unit
  2226  	now := k.clock.Now()
  2227  	for _, p := range podsList.Items {
  2228  		var ports []string
  2229  		for _, c := range p.Spec.Containers {
  2230  			for _, p := range c.Ports {
  2231  				ports = append(ports, fmt.Sprintf("%v/%v", p.ContainerPort, p.Protocol))
  2232  			}
  2233  		}
  2234  
  2235  		eventGetter := func() ([]core.Event, error) {
  2236  			return k.getEvents(p.Name, "Pod")
  2237  		}
  2238  
  2239  		terminated := p.DeletionTimestamp != nil
  2240  		statusMessage, unitStatus, since, err := resources.PodToJujuStatus(p, now, eventGetter)
  2241  
  2242  		if err != nil {
  2243  			return nil, errors.Trace(err)
  2244  		}
  2245  		unitInfo := caas.Unit{
  2246  			Id:       providerID(&p),
  2247  			Address:  p.Status.PodIP,
  2248  			Ports:    ports,
  2249  			Dying:    terminated,
  2250  			Stateful: isStateful(&p),
  2251  			Status: status.StatusInfo{
  2252  				Status:  unitStatus,
  2253  				Message: statusMessage,
  2254  				Since:   &since,
  2255  			},
  2256  		}
  2257  
  2258  		volumesByName := make(map[string]core.Volume)
  2259  		for _, pv := range p.Spec.Volumes {
  2260  			volumesByName[pv.Name] = pv
  2261  		}
  2262  
  2263  		// Gather info about how filesystems are attached/mounted to the pod.
  2264  		// The mount name represents the filesystem tag name used by Juju.
  2265  		for _, volMount := range p.Spec.Containers[0].VolumeMounts {
  2266  			vol, ok := volumesByName[volMount.Name]
  2267  			if !ok {
  2268  				logger.Warningf("volume for volume mount %q not found", volMount.Name)
  2269  				continue
  2270  			}
  2271  			var fsInfo *caas.FilesystemInfo
  2272  			if vol.PersistentVolumeClaim != nil && vol.PersistentVolumeClaim.ClaimName != "" {
  2273  				fsInfo, err = k.volumeInfoForPVC(vol, volMount, vol.PersistentVolumeClaim.ClaimName, now)
  2274  			} else if vol.EmptyDir != nil {
  2275  				fsInfo, err = k.volumeInfoForEmptyDir(vol, volMount, now)
  2276  			} else {
  2277  				// Ignore volumes which are not Juju managed filesystems.
  2278  				logger.Debugf("ignoring blank EmptyDir, PersistentVolumeClaim or ClaimName")
  2279  				continue
  2280  			}
  2281  			if err != nil {
  2282  				return nil, errors.Annotatef(err, "finding filesystem info for %v", volMount.Name)
  2283  			}
  2284  			if fsInfo == nil {
  2285  				continue
  2286  			}
  2287  			if fsInfo.StorageName == "" {
  2288  				if valid := constants.LegacyPVNameRegexp.MatchString(volMount.Name); valid {
  2289  					fsInfo.StorageName = constants.LegacyPVNameRegexp.ReplaceAllString(volMount.Name, "$storageName")
  2290  				} else if valid := constants.PVNameRegexp.MatchString(volMount.Name); valid {
  2291  					fsInfo.StorageName = constants.PVNameRegexp.ReplaceAllString(volMount.Name, "$storageName")
  2292  				}
  2293  			}
  2294  			logger.Debugf("filesystem info for %v: %+v", volMount.Name, *fsInfo)
  2295  			unitInfo.FilesystemInfo = append(unitInfo.FilesystemInfo, *fsInfo)
  2296  		}
  2297  		units = append(units, unitInfo)
  2298  	}
  2299  	return units, nil
  2300  }
  2301  
  2302  // ListPods filters a list of pods for the provided namespace and labels.
  2303  func (k *kubernetesClient) ListPods(namespace string, selector k8slabels.Selector) ([]core.Pod, error) {
  2304  	listOps := v1.ListOptions{
  2305  		LabelSelector: selector.String(),
  2306  	}
  2307  	list, err := k.client().CoreV1().Pods(namespace).List(context.TODO(), listOps)
  2308  	if err != nil {
  2309  		return nil, errors.Trace(err)
  2310  	}
  2311  	if len(list.Items) == 0 {
  2312  		return nil, errors.NotFoundf("pods with selector %q", selector)
  2313  	}
  2314  	return list.Items, nil
  2315  }
  2316  
  2317  func (k *kubernetesClient) getPod(podName string) (*core.Pod, error) {
  2318  	if k.namespace == "" {
  2319  		return nil, errNoNamespace
  2320  	}
  2321  	pods := k.client().CoreV1().Pods(k.namespace)
  2322  	pod, err := pods.Get(context.TODO(), podName, v1.GetOptions{})
  2323  	if k8serrors.IsNotFound(err) {
  2324  		return nil, errors.NotFoundf("pod %q", podName)
  2325  	} else if err != nil {
  2326  		return nil, errors.Trace(err)
  2327  	}
  2328  	return pod, nil
  2329  }
  2330  
  2331  func (k *kubernetesClient) getStatefulSetStatus(ss *apps.StatefulSet) (string, status.Status, error) {
  2332  	terminated := ss.DeletionTimestamp != nil
  2333  	jujuStatus := status.Waiting
  2334  	if terminated {
  2335  		jujuStatus = status.Terminated
  2336  	}
  2337  	if ss.Status.ReadyReplicas == ss.Status.Replicas {
  2338  		jujuStatus = status.Active
  2339  	}
  2340  	return k.getStatusFromEvents(ss.Name, "StatefulSet", jujuStatus)
  2341  }
  2342  
  2343  func (k *kubernetesClient) getDeploymentStatus(deployment *apps.Deployment) (string, status.Status, error) {
  2344  	terminated := deployment.DeletionTimestamp != nil
  2345  	jujuStatus := status.Waiting
  2346  	if terminated {
  2347  		jujuStatus = status.Terminated
  2348  	}
  2349  	if deployment.Status.ReadyReplicas == deployment.Status.Replicas {
  2350  		jujuStatus = status.Active
  2351  	}
  2352  	return k.getStatusFromEvents(deployment.Name, "Deployment", jujuStatus)
  2353  }
  2354  
  2355  func (k *kubernetesClient) getDaemonSetStatus(ds *apps.DaemonSet) (string, status.Status, error) {
  2356  	terminated := ds.DeletionTimestamp != nil
  2357  	jujuStatus := status.Waiting
  2358  	if terminated {
  2359  		jujuStatus = status.Terminated
  2360  	}
  2361  	if ds.Status.NumberReady == ds.Status.DesiredNumberScheduled {
  2362  		jujuStatus = status.Active
  2363  	}
  2364  	return k.getStatusFromEvents(ds.Name, "DaemonSet", jujuStatus)
  2365  }
  2366  
  2367  func (k *kubernetesClient) getStatusFromEvents(name, kind string, jujuStatus status.Status) (string, status.Status, error) {
  2368  	events, err := k.getEvents(name, kind)
  2369  	if err != nil {
  2370  		return "", "", errors.Trace(err)
  2371  	}
  2372  	var statusMessage string
  2373  	// Take the most recent event.
  2374  	if count := len(events); count > 0 {
  2375  		evt := events[count-1]
  2376  		if jujuStatus == "" {
  2377  			if evt.Type == core.EventTypeWarning && evt.Reason == "FailedCreate" {
  2378  				jujuStatus = status.Error
  2379  				statusMessage = evt.Message
  2380  			}
  2381  		}
  2382  	}
  2383  	return statusMessage, jujuStatus, nil
  2384  }
  2385  
  2386  // filesetConfigMap returns a *core.ConfigMap for a pod
  2387  // of the specified unit, with the specified files.
  2388  func filesetConfigMap(configMapName string, labels, annotations map[string]string, files *specs.FileSet) *core.ConfigMap {
  2389  	result := &core.ConfigMap{
  2390  		ObjectMeta: v1.ObjectMeta{
  2391  			Name:        configMapName,
  2392  			Labels:      labels,
  2393  			Annotations: annotations,
  2394  		},
  2395  		Data: map[string]string{},
  2396  	}
  2397  	for _, f := range files.Files {
  2398  		result.Data[f.Path] = f.Content
  2399  	}
  2400  	return result
  2401  }
  2402  
  2403  // workloadSpec represents the k8s resources need to be created for the workload.
  2404  type workloadSpec struct {
  2405  	Pod     k8sspecs.PodSpecWithAnnotations
  2406  	Service *specs.ServiceSpec
  2407  
  2408  	Secrets                         []k8sspecs.K8sSecret
  2409  	Services                        []k8sspecs.K8sService
  2410  	ConfigMaps                      map[string]specs.ConfigMap
  2411  	ServiceAccounts                 []k8sspecs.K8sRBACSpecConverter
  2412  	CustomResourceDefinitions       []k8sspecs.K8sCustomResourceDefinition
  2413  	CustomResources                 map[string][]unstructured.Unstructured
  2414  	MutatingWebhookConfigurations   []k8sspecs.K8sMutatingWebhook
  2415  	ValidatingWebhookConfigurations []k8sspecs.K8sValidatingWebhook
  2416  	IngressResources                []k8sspecs.K8sIngress
  2417  }
  2418  
  2419  func processContainers(deploymentName string, podSpec *specs.PodSpec, spec *core.PodSpec) error {
  2420  
  2421  	type containers struct {
  2422  		Containers     []specs.ContainerSpec
  2423  		InitContainers []specs.ContainerSpec
  2424  	}
  2425  
  2426  	var cs containers
  2427  	for _, c := range podSpec.Containers {
  2428  		if c.Init {
  2429  			cs.InitContainers = append(cs.InitContainers, c)
  2430  		} else {
  2431  			cs.Containers = append(cs.Containers, c)
  2432  		}
  2433  	}
  2434  
  2435  	// Fill out the easy bits using a template.
  2436  	var buf bytes.Buffer
  2437  	if err := defaultPodTemplate.Execute(&buf, cs); err != nil {
  2438  		logger.Debugf("unable to execute template for containers: %+v, err: %+v", cs, err)
  2439  		return errors.Trace(err)
  2440  	}
  2441  
  2442  	workloadSpecString := buf.String()
  2443  	decoder := k8syaml.NewYAMLOrJSONDecoder(strings.NewReader(workloadSpecString), len(workloadSpecString))
  2444  	if err := decoder.Decode(&spec); err != nil {
  2445  		logger.Debugf("unable to parse pod spec, unit spec: \n%v", workloadSpecString)
  2446  		return errors.Trace(err)
  2447  	}
  2448  
  2449  	// Now fill in the hard bits progamatically.
  2450  	if err := populateContainerDetails(deploymentName, spec, spec.Containers, cs.Containers); err != nil {
  2451  		return errors.Trace(err)
  2452  	}
  2453  	if err := populateContainerDetails(deploymentName, spec, spec.InitContainers, cs.InitContainers); err != nil {
  2454  		return errors.Trace(err)
  2455  	}
  2456  	return nil
  2457  }
  2458  
  2459  func prepareWorkloadSpec(
  2460  	appName, deploymentName string, podSpec *specs.PodSpec, imageDetails coreresources.DockerImageDetails,
  2461  ) (*workloadSpec, error) {
  2462  	var spec workloadSpec
  2463  	if err := processContainers(deploymentName, podSpec, &spec.Pod.PodSpec); err != nil {
  2464  		logger.Errorf("unable to parse %q pod spec: \n%+v", appName, *podSpec)
  2465  		return nil, errors.Annotatef(err, "processing container specs for app %q", appName)
  2466  	}
  2467  	if err := ensureJujuInitContainer(&spec.Pod.PodSpec, imageDetails.RegistryPath); err != nil {
  2468  		return nil, errors.Annotatef(err, "adding init container for app %q", appName)
  2469  	}
  2470  	if imageDetails.IsPrivate() {
  2471  		spec.Pod.PodSpec.ImagePullSecrets = append(
  2472  			spec.Pod.PodSpec.ImagePullSecrets,
  2473  			core.LocalObjectReference{Name: constants.CAASImageRepoSecretName},
  2474  		)
  2475  	}
  2476  
  2477  	spec.Service = podSpec.Service
  2478  	spec.ConfigMaps = podSpec.ConfigMaps
  2479  	if podSpec.ServiceAccount != nil {
  2480  		// Use application name for the prime service account name.
  2481  		podSpec.ServiceAccount.SetName(appName)
  2482  		primeSA, err := k8sspecs.PrimeServiceAccountToK8sRBACResources(*podSpec.ServiceAccount)
  2483  		if err != nil {
  2484  			return nil, errors.Annotatef(err, "converting prime service account for app %q", appName)
  2485  		}
  2486  		spec.ServiceAccounts = append(spec.ServiceAccounts, primeSA)
  2487  
  2488  		spec.Pod.ServiceAccountName = podSpec.ServiceAccount.GetName()
  2489  		spec.Pod.AutomountServiceAccountToken = podSpec.ServiceAccount.AutomountServiceAccountToken
  2490  	}
  2491  	if podSpec.ProviderPod != nil {
  2492  		pSpec, ok := podSpec.ProviderPod.(*k8sspecs.K8sPodSpec)
  2493  		if !ok {
  2494  			return nil, errors.Errorf("unexpected kubernetes pod spec type %T", podSpec.ProviderPod)
  2495  		}
  2496  
  2497  		k8sResources := pSpec.KubernetesResources
  2498  		if k8sResources != nil {
  2499  			spec.Secrets = k8sResources.Secrets
  2500  			spec.Services = k8sResources.Services
  2501  			spec.CustomResourceDefinitions = k8sResources.CustomResourceDefinitions
  2502  			spec.CustomResources = k8sResources.CustomResources
  2503  			spec.MutatingWebhookConfigurations = k8sResources.MutatingWebhookConfigurations
  2504  			spec.ValidatingWebhookConfigurations = k8sResources.ValidatingWebhookConfigurations
  2505  			spec.IngressResources = k8sResources.IngressResources
  2506  			if k8sResources.Pod != nil {
  2507  				spec.Pod.Labels = utils.LabelsMerge(nil, k8sResources.Pod.Labels)
  2508  				spec.Pod.Annotations = k8sResources.Pod.Annotations.Copy()
  2509  				spec.Pod.RestartPolicy = k8sResources.Pod.RestartPolicy
  2510  				spec.Pod.ActiveDeadlineSeconds = k8sResources.Pod.ActiveDeadlineSeconds
  2511  				spec.Pod.TerminationGracePeriodSeconds = k8sResources.Pod.TerminationGracePeriodSeconds
  2512  				spec.Pod.SecurityContext = k8sResources.Pod.SecurityContext
  2513  				spec.Pod.ReadinessGates = k8sResources.Pod.ReadinessGates
  2514  				spec.Pod.DNSPolicy = k8sResources.Pod.DNSPolicy
  2515  				spec.Pod.HostNetwork = k8sResources.Pod.HostNetwork
  2516  				spec.Pod.HostPID = k8sResources.Pod.HostPID
  2517  				spec.Pod.PriorityClassName = k8sResources.Pod.PriorityClassName
  2518  				spec.Pod.Priority = k8sResources.Pod.Priority
  2519  			}
  2520  			spec.ServiceAccounts = append(spec.ServiceAccounts, &k8sResources.K8sRBACResources)
  2521  		}
  2522  	}
  2523  	return &spec, nil
  2524  }
  2525  
  2526  func boolPtr(b bool) *bool {
  2527  	return &b
  2528  }
  2529  
  2530  func defaultSecurityContext() *core.SecurityContext {
  2531  	// TODO(caas): consider locking this down more but charms will break
  2532  	return &core.SecurityContext{
  2533  		AllowPrivilegeEscalation: boolPtr(true), // allow privilege for juju run and actions.
  2534  		ReadOnlyRootFilesystem:   boolPtr(false),
  2535  		RunAsNonRoot:             boolPtr(false),
  2536  	}
  2537  }
  2538  
  2539  func populateContainerDetails(deploymentName string, pod *core.PodSpec, podContainers []core.Container, containers []specs.ContainerSpec) (err error) {
  2540  	for i, c := range containers {
  2541  		pc := &podContainers[i]
  2542  		if c.Image != "" {
  2543  			logger.Warningf("Image parameter deprecated, use ImageDetails")
  2544  			pc.Image = c.Image
  2545  		} else {
  2546  			pc.Image = c.ImageDetails.ImagePath
  2547  		}
  2548  		if c.ImageDetails.Password != "" {
  2549  			pod.ImagePullSecrets = append(pod.ImagePullSecrets, core.LocalObjectReference{Name: appSecretName(deploymentName, c.Name)})
  2550  		}
  2551  		if c.ImagePullPolicy != "" {
  2552  			pc.ImagePullPolicy = core.PullPolicy(c.ImagePullPolicy)
  2553  		}
  2554  
  2555  		if pc.Env, pc.EnvFrom, err = k8sspecs.ContainerConfigToK8sEnvConfig(c.EnvConfig); err != nil {
  2556  			return errors.Trace(err)
  2557  		}
  2558  
  2559  		pc.SecurityContext = defaultSecurityContext()
  2560  		if c.ProviderContainer == nil {
  2561  			continue
  2562  		}
  2563  		spec, ok := c.ProviderContainer.(*k8sspecs.K8sContainerSpec)
  2564  		if !ok {
  2565  			return errors.Errorf("unexpected kubernetes container spec type %T", c.ProviderContainer)
  2566  		}
  2567  		if spec.LivenessProbe != nil {
  2568  			pc.LivenessProbe = spec.LivenessProbe
  2569  		}
  2570  		if spec.ReadinessProbe != nil {
  2571  			pc.ReadinessProbe = spec.ReadinessProbe
  2572  		}
  2573  		if spec.StartupProbe != nil {
  2574  			pc.StartupProbe = spec.StartupProbe
  2575  		}
  2576  		if spec.SecurityContext != nil {
  2577  			pc.SecurityContext = spec.SecurityContext
  2578  		}
  2579  	}
  2580  	return nil
  2581  }
  2582  
  2583  // legacyAppName returns true if there are any artifacts for
  2584  // appName which indicate that this deployment was for Juju 2.5.0.
  2585  func (k *kubernetesClient) legacyAppName(appName string) bool {
  2586  	legacyName := "juju-operator-" + appName
  2587  	_, err := k.getStatefulSet(legacyName)
  2588  	return err == nil
  2589  }
  2590  
  2591  func (k *kubernetesClient) operatorName(appName string) string {
  2592  	if k.legacyAppName(appName) {
  2593  		return "juju-operator-" + appName
  2594  	}
  2595  	return appName + "-operator"
  2596  }
  2597  
  2598  func (k *kubernetesClient) deploymentName(appName string, legacySupport bool) string {
  2599  	if !legacySupport {
  2600  		// No need to check old operator statefulset for brand new features like raw k8s spec.
  2601  		return appName
  2602  	}
  2603  	if k.legacyAppName(appName) {
  2604  		return "juju-" + appName
  2605  	}
  2606  	return appName
  2607  }
  2608  
  2609  // SupportedFeatures implements environs.SupportedFeatureEnumerator.
  2610  func (k *kubernetesClient) SupportedFeatures() (assumes.FeatureSet, error) {
  2611  	var fs assumes.FeatureSet
  2612  
  2613  	k8sAPIVersion, err := k.Version()
  2614  	if err != nil {
  2615  		return fs, errors.Annotatef(err, "querying kubernetes API version")
  2616  	}
  2617  
  2618  	fs.Add(
  2619  		assumes.Feature{
  2620  			Name:        "k8s-api",
  2621  			Description: assumes.UserFriendlyFeatureDescriptions["k8s-api"],
  2622  			Version:     k8sAPIVersion,
  2623  		},
  2624  	)
  2625  	return fs, nil
  2626  }
  2627  
  2628  func isLegacyName(resourceName string) bool {
  2629  	return strings.HasPrefix(resourceName, "juju-")
  2630  }
  2631  
  2632  func operatorConfigMapName(operatorName string) string {
  2633  	return operatorName + "-config"
  2634  }
  2635  
  2636  func applicationConfigMapName(deploymentName, fileSetName string) string {
  2637  	return fmt.Sprintf("%v-%v-config", deploymentName, fileSetName)
  2638  }
  2639  
  2640  func appSecretName(deploymentName, containerName string) string {
  2641  	// A pod may have multiple containers with different images and thus different secrets
  2642  	return deploymentName + "-" + containerName + "-secret"
  2643  }
  2644  
  2645  func mergeDeviceConstraints(device devices.KubernetesDeviceParams, resources *core.ResourceRequirements) error {
  2646  	if resources.Limits == nil {
  2647  		resources.Limits = core.ResourceList{}
  2648  	}
  2649  	if resources.Requests == nil {
  2650  		resources.Requests = core.ResourceList{}
  2651  	}
  2652  
  2653  	resourceName := core.ResourceName(device.Type)
  2654  	if v, ok := resources.Limits[resourceName]; ok {
  2655  		return errors.NotValidf("resource limit for %q has already been set to %v! resource limit %q", resourceName, v, resourceName)
  2656  	}
  2657  	if v, ok := resources.Requests[resourceName]; ok {
  2658  		return errors.NotValidf("resource request for %q has already been set to %v! resource limit %q", resourceName, v, resourceName)
  2659  	}
  2660  	// GPU request/limit have to be set to same value equals to the Count.
  2661  	// - https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/#clusters-containing-different-types-of-nvidia-gpus
  2662  	resources.Limits[resourceName] = *resource.NewQuantity(device.Count, resource.DecimalSI)
  2663  	resources.Requests[resourceName] = *resource.NewQuantity(device.Count, resource.DecimalSI)
  2664  	return nil
  2665  }
  2666  
  2667  func buildNodeSelector(nodeLabel string) map[string]string {
  2668  	// TODO(caas): to support GKE, set it to `cloud.google.com/gke-accelerator`,
  2669  	// current only set to generic `accelerator` because we do not have k8s provider concept yet.
  2670  	key := "accelerator"
  2671  	return map[string]string{key: nodeLabel}
  2672  }
  2673  
  2674  func getNodeSelectorFromDeviceConstraints(devices []devices.KubernetesDeviceParams) (string, error) {
  2675  	var nodeSelector string
  2676  	for _, device := range devices {
  2677  		if device.Attributes == nil {
  2678  			continue
  2679  		}
  2680  		if label, ok := device.Attributes[gpuAffinityNodeSelectorKey]; ok {
  2681  			if nodeSelector != "" && nodeSelector != label {
  2682  				return "", errors.NotValidf(
  2683  					"node affinity labels have to be same for all device constraints in same pod - containers in same pod are scheduled in same node.")
  2684  			}
  2685  			nodeSelector = label
  2686  		}
  2687  	}
  2688  	return nodeSelector, nil
  2689  }
  2690  
  2691  func headlessServiceName(deploymentName string) string {
  2692  	return fmt.Sprintf("%s-endpoints", deploymentName)
  2693  }
  2694  
  2695  func providerID(pod *core.Pod) string {
  2696  	// Pods managed by a stateful set use the pod name
  2697  	// as the provider id as this is stable across pod restarts.
  2698  	if isStateful(pod) {
  2699  		return pod.Name
  2700  	}
  2701  	return string(pod.GetUID())
  2702  }
  2703  
  2704  func isStateful(pod *core.Pod) bool {
  2705  	for _, ref := range pod.OwnerReferences {
  2706  		if ref.Kind == "StatefulSet" {
  2707  			return true
  2708  		}
  2709  	}
  2710  	return false
  2711  }