github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/acceptancetests/jujupy/k8s_provider/eks.py (about)

     1  # This file is part of JujuPy, a library for driving the Juju CLI.
     2  # Copyright 2013-2020 Canonical Ltd.
     3  #
     4  # This program is free software: you can redistribute it and/or modify it
     5  # under the terms of the Lesser GNU General Public License version 3, as
     6  # published by the Free Software Foundation.
     7  #
     8  # This program is distributed in the hope that it will be useful, but WITHOUT
     9  # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
    10  # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser
    11  # GNU General Public License for more details.
    12  #
    13  # You should have received a copy of the Lesser GNU General Public License
    14  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  # Functionality for handling installed or other juju binaries
    17  # (including paths etc.)
    18  
    19  
    20  from __future__ import print_function
    21  
    22  import json
    23  import logging
    24  import os
    25  import shutil
    26  import subprocess
    27  from time import sleep
    28  
    29  import yaml
    30  
    31  from jujupy.utility import until_timeout
    32  
    33  from .base import Base, K8sProviderType
    34  from .factory import register_provider
    35  
    36  logger = logging.getLogger(__name__)
    37  
    38  
    39  @register_provider
    40  class EKS(Base):
    41  
    42      name = K8sProviderType.EKS
    43      location = None
    44      parameters = None
    45  
    46      def __init__(self, bs_manager, cluster_name=None, enable_rbac=False, timeout=1800):
    47          super().__init__(bs_manager, cluster_name, enable_rbac, timeout)
    48  
    49          self._eksctl_bin = os.path.join(self.juju_home, 'eksctl')
    50          self._ensure_eksctl_bin()
    51          self.default_storage_class_name = ''
    52          self.__init_client(bs_manager.client.env)
    53  
    54      def __init_client(self, env):
    55          credential = {
    56              'AWS_ACCESS_KEY_ID': env._config['access-key'],
    57              'AWS_SECRET_ACCESS_KEY': env._config['secret-key'],
    58          }
    59          for k, v in credential.items():
    60              os.environ[k] = v
    61  
    62          self.location = env._config['location']
    63  
    64          # list all running clusters.
    65          logger.info(
    66              'Running eks clusters in %s: \n%s', self.location,
    67              yaml.dump(self.list_clusters(self.location))
    68          )
    69  
    70      def _ensure_cluster_stack(self):
    71          self.provision_eks()
    72  
    73      def _ensure_eksctl_bin(self):
    74          path = shutil.which('eksctl')
    75          if path is not None:
    76              self._eksctl_bin = path
    77          else:
    78              self.sh(
    79                  '''curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp && mv /tmp/eksctl %s
    80  ''' % self._eksctl_bin, shell=True, ignore_quote=True)
    81  
    82      def eksctl(self, *args):
    83          return self.sh(self._eksctl_bin, *args)
    84  
    85      def _tear_down_substrate(self):
    86          logger.info("Deleting the EKS instance {0}".format(self.cluster_name))
    87          try:
    88              o = self.eksctl('delete', 'cluster', self.cluster_name, '--region', self.location)
    89              logger.info("cluster %s has been deleted -> \n%s", self.cluster_name, o)
    90          except Exception as e:
    91              if is_404(e):
    92                  return
    93              logger.error(e)
    94              raise
    95  
    96      def list_clusters(self, region):
    97          return json.loads(
    98              self.eksctl('get', 'cluster', '--region', region, '-o', 'json'),
    99          )
   100  
   101      def get_lb_svc_address(self, svc_name, namespace):
   102          return json.loads(
   103              self.kubectl('-n', namespace, 'get', 'svc', svc_name, '-o', 'json')
   104          )['status']['loadBalancer']['ingress'][0]['hostname']
   105  
   106      def _ensure_kube_dir(self):
   107          logger.info("Writing kubeconfig to %s" % self.kube_config_path)
   108          self.eksctl(
   109              'utils', 'write-kubeconfig', '--cluster', self.cluster_name,
   110              '--region', self.location, '--kubeconfig', self.kube_config_path,
   111          )
   112  
   113          with open(self.kube_config_path, 'r') as f:
   114              self.kubeconfig_cluster_name = yaml.load(f, yaml.SafeLoader)['contexts'][0]['context']['cluster']
   115  
   116          # ensure kubectl
   117          self._ensure_kubectl_bin()
   118  
   119      def _ensure_cluster_config(self):
   120          ...
   121  
   122      def _node_address_getter(self, node):
   123          raise NotImplementedError()
   124  
   125      def _get_cluster(self, name):
   126          return self.eksctl('get', 'cluster', '--name', self.cluster_name, '--region', self.location, '-o', 'json')
   127  
   128      def provision_eks(self):
   129          def log_remaining(remaining, msg=''):
   130              sleep(3)
   131              if remaining % 30 == 0:
   132                  msg += ' timeout in %ss...' % remaining
   133                  logger.info(msg)
   134  
   135          # do pre cleanup;
   136          self._tear_down_substrate()
   137  
   138          for remaining in until_timeout(600):
   139              # wait for the existing cluster to be deleted.
   140              try:
   141                  self._get_cluster(self.cluster_name)
   142              except Exception as e:
   143                  if is_404(e):
   144                      break
   145                  raise
   146              else:
   147                  log_remaining(remaining)
   148  
   149          # provision cluster.
   150          logger.info('Creating cluster -> %s', self.cluster_name)
   151          try:
   152              o = self.eksctl(
   153                  'create', 'cluster',
   154                  '--name', self.cluster_name,
   155                  '--version', '1.27',
   156                  '--region', self.location,
   157                  '--nodes', 3,
   158                  '--nodes-min', 1,
   159                  '--nodes-max', 3,
   160                  '--ssh-access',
   161                  '--ssh-public-key=' + os.path.expanduser('~/.ssh/id_rsa.pub'),
   162                  '--managed',
   163              )
   164              logger.info("cluster %s has been successfully provisioned -> \n%s", self.cluster_name, o)
   165          except subprocess.CalledProcessError as e:
   166              logger.error('Error attempting to create the EKS instance %s', e.__dict__)
   167              raise e
   168  
   169  
   170  def is_404(err):
   171      try:
   172          err_msg = err.output.decode()
   173          return any([keyword in err_msg for keyword in ['404', 'does not exist', 'ResourceNotFoundException']])
   174      except Exception:
   175          return False