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