def get_certification_issuer(self, track: str) -> Optional[str]: logger.info(icon=f"{self.ICON} 🏵️️", title="Checking certification issuer", end="") raise_exception = False if settings.K8S_CLUSTER_ISSUER: cert_issuer: str = settings.K8S_CLUSTER_ISSUER logger.info(message=" (settings): ", end="") raise_exception = True else: cert_issuer = f"certificate-letsencrypt-{track}" logger.info(message=" (track): ", end="") os_command = ["kubectl", "get", "clusterissuer", cert_issuer] result = run_os_command(os_command, shell=True) if not result.return_code: logger.success(message=cert_issuer) return cert_issuer else: error_message = f'No issuer "{cert_issuer}" found, using cluster defaults' if raise_exception: logger.error(message=error_message, raise_exception=True) else: logger.info(message=error_message) return None
def update_repos(self) -> None: logger.info(icon=f"{self.ICON} 🔄", title="Updating Helm repos: ", end="") result = run_os_command(["helm", "repo", "update"]) if not result.return_code: logger.success() else: logger.std(result, raise_exception=True)
def delete( self, resource: str, name: Optional[str] = None, labels: Optional[Dict[str, str]] = None, namespace: str = settings.K8S_NAMESPACE, ) -> None: os_command = [ "kubectl", "delete", resource, "--ignore-not-found", "--wait=true", f"--namespace={namespace}", ] logger.info(icon=f"{self.ICON} 🗑️ ", title=f"Removing {resource}", end="") if labels: labels_str = self.labels_to_string(labels) os_command += ["-l", labels_str] logger.info(title=f" with labels {labels_str}", end="") if name: os_command += [name] logger.info(title=f" with name '{name}'", end="") logger.info(": ", end="") result = run_os_command(os_command, shell=True) if not result.return_code: logger.success() else: logger.std(result, raise_exception=True)
def update_submodules(self, depth: int = 0, jobs: int = 0) -> None: """ Update all submodules Returns: None """ os_command = [ "git", "submodule", "update", "--init", "--recursive", ] if depth: os_command += ["--depth", f"{depth}"] if jobs: os_command += ["--jobs", f"{jobs}"] logger.info(icon=f"{self.ICON} 🌱", title="Updating submodules: ", end="") result = run_os_command(os_command) if result.return_code: logger.std(result, raise_exception=True) logger.success()
def remove_repo(self, repo_name: str) -> None: logger.info( icon=f"{self.ICON} âž–", title=f"Removing Helm repo {repo_name}: ", end="", ) result = run_os_command(["helm", "repo", "remove", repo_name]) if not result.return_code: logger.success() else: logger.std(result, raise_exception=True)
def delete_image(self, image: DockerImage) -> None: logger.warning(icon=f"{self.ICON}", message="Removing Docker image") for tag in image.tags: logger.info(message=f"\t {image.repository}:{tag}: ", end="") delete_command = ["docker", "rmi", f"{image.repository}:{tag}"] result = run_os_command(delete_command, shell=False) if result.return_code: logger.std(result, raise_exception=False) else: logger.success()
def pull_image(self, image: str) -> bool: logger.info(icon=f"{self.ICON} ⏬", title=f"Pulling {image}:", end=" ") pull_command = ["docker", "pull", image] result = run_os_command(pull_command, shell=False) if result.return_code: logger.std(result, raise_exception=False) else: logger.success() return True return False
def add_repo(self, repo_name: str, repo_url: str, update: bool = True) -> None: logger.info( icon=f"{self.ICON} âž•", title=f"Adding Helm repo {repo_url} with name {repo_name}: ", end="", ) result = run_os_command(["helm", "repo", "add", repo_name, repo_url]) if not result.return_code: logger.success() else: logger.std(result, raise_exception=True) if update: self.update_repos()
def _create_basic_auth_data( self, basic_auth_users: List[BasicAuthUser] = settings.K8S_INGRESS_BASIC_AUTH ) -> Dict[str, str]: """ Create secret data from list of `BasicAuthUser` The user credentials from the list of users will be encrypted and added to a temporary file using the `htpasswd` tool from Apache. The file is then read and base64 encoded (as required by Kubernetes secrets). Args: basic_auth_users: List of `BasicAuthUser`s Returns: A dict with the key `auth` and base64 content of a htpasswd file as value """ logger.info(icon=f"{self.ICON} 🔨", title="Generating basic auth data: ", end="") if not basic_auth_users: return {} with tempfile.NamedTemporaryFile() as f: passwd_path = Path(f.name) for i, user in enumerate(basic_auth_users): os_command = ["htpasswd", "-b"] if i == 0: os_command.append("-c") os_command += [str(passwd_path), user.username, user.password] result = run_os_command(os_command) if result.return_code: logger.error( message= "The 'htpasswd' command failed to create an entry", raise_exception=True, ) encoded_file = self._b64_encode_file(passwd_path) logger.success() logger.info( message= f"\t {len(settings.K8S_INGRESS_BASIC_AUTH)} users will be added to basic auth" ) return {"auth": encoded_file}
def create_namespace(self, namespace: str = settings.K8S_NAMESPACE) -> str: """ Create a Kubernetes namespace Args: namespace: Name of the namespace to create Returns: On success, returns the name of the namespace Raises: ApiException: If the namespace creation fails by other means than a namespace conflict, something that happens if the namespace already exists. """ v1 = k8s_client.CoreV1Api(self.client) logger.info(icon=f"{self.ICON} 🔨", title=f"Checking namespace {namespace}: ", end="") try: v1.read_namespace(name=namespace) except ApiException as e: self._handle_api_error(e) if e.status != 404: raise e else: logger.success() return namespace v1_metadata = k8s_client.V1ObjectMeta( labels=self.get_namespace_labels(), name=namespace, ) v1_namespace = k8s_client.V1Namespace(metadata=v1_metadata) try: v1.create_namespace(v1_namespace) except ApiException as e: self._handle_api_error(e) if e.status != 409: # Namespace exists raise e logger.success() return namespace
def setup_buildkit(self, name: str = "kolgabk") -> None: setup_command = [ "docker", "buildx", "create", "--name", name, "--use", ] result = run_os_command(setup_command) if result.return_code: logger.std(result, raise_exception=True) else: logger.success( icon=f"{self.ICON} 🔑", message= f"New buildx builder instance is set up (Instance name: {name})", )
def create_client(self, track: str) -> k8s_client.ApiClient: try: kubeconfig, method = settings.setup_kubeconfig(track) except NoClusterConfigError as exc: logger.error( icon=f"{self.ICON} 🔑", message="Can't log in to Kubernetes cluster, all auth methods exhausted", error=exc, raise_exception=True, ) logger.success( icon=f"{self.ICON} 🔑", message=f"Using {method} for Kubernetes auth" ) config = k8s_client.Configuration() k8s_config.load_kube_config(client_configuration=config, config_file=kubeconfig) return k8s_client.ApiClient(configuration=config)
def get( self, resource: str, name: Optional[str] = None, labels: Optional[Dict[str, str]] = None, namespace: str = settings.K8S_NAMESPACE, raise_exception: bool = True, ) -> SubprocessResult: os_command = ["kubectl", "get"] logger.info(icon=f"{self.ICON} ℹ️ ", title=f"Getting {resource}", end="") os_command += self._resource_command( resource=resource, name=name, labels=labels, namespace=namespace ) logger.info(": ", end="") result = run_os_command(os_command, shell=True) # nosec if not result.return_code: logger.success() else: logger.std(result, raise_exception=raise_exception) return result
def logs( self, labels: Optional[Dict[str, str]] = None, since_time: Optional[str] = None, namespace: str = settings.K8S_NAMESPACE, print_result: bool = True, raise_exception: bool = True, ) -> SubprocessResult: os_command = [ "kubectl", "logs", f"--namespace={namespace}", "--prefix=true", "--timestamps=true", "--tail=100", ] logger.info(icon=f"{self.ICON} 📋️️ ", title="Getting logs for resource: ", end="") if labels: labels_str = self.labels_to_string(labels) os_command += ["-l", labels_str] logger.info(title=f" with labels {labels_str}", end="") if since_time: os_command += [f"--since-time={since_time}"] logger.info(title=f" since {since_time}", end="") result = run_os_command(os_command, shell=True) if not result.return_code: logger.success() if print_result: logger.std(result) else: logger.std(result, raise_exception=raise_exception) return result
def create_secret( self, data: Dict[str, str], namespace: str, track: str, project: Project, secret_name: str, encode: bool = True, ) -> None: deploy_name = get_deploy_name(track=track, postfix=project.name) v1 = k8s_client.CoreV1Api(self.client) v1_metadata = k8s_client.V1ObjectMeta(name=secret_name, namespace=namespace, labels={"release": deploy_name}) if encode: encoded_data = self._encode_secret(data) else: encoded_data = data body = k8s_client.V1Secret(data=encoded_data, metadata=v1_metadata, type="generic") logger.info( icon=f"{self.ICON} 🔨", title= f"Creating secret '{secret_name}' for namespace '{namespace}': ", end="", ) try: v1.create_namespaced_secret(namespace=namespace, body=body) except ApiException: try: v1.replace_namespaced_secret(name=secret_name, namespace=namespace, body=body) except ApiException as e: self._handle_api_error(e, raise_client_exception=True) logger.success()
def login( self, username: str = settings.CONTAINER_REGISTRY_USER, password: str = settings.CONTAINER_REGISTRY_PASSWORD, registry: str = settings.CONTAINER_REGISTRY, ) -> None: login_command = [ "docker", "login", "-u", username, "-p", password, registry, ] result = run_os_command(login_command) if result.return_code: logger.std(result, raise_exception=True) else: logger.success( icon=f"{self.ICON} 🔑", message=f"Logged in to registry (User: {username})", )
def upgrade_chart( self, name: str, values: HelmValues, namespace: str, chart: str = "", chart_path: Optional[Path] = None, values_files: Optional[List[Path]] = None, install: bool = True, version: Optional[str] = None, raise_exception: bool = True, ) -> SubprocessResult: if chart_path: if not chart_path.is_absolute(): chart_path = settings.devops_root_path / chart_path if not chart_path.exists(): logger.error( message=f"Path '{str(chart_path)}' does not exist", error=OSError(), raise_exception=True, ) chart = str(chart_path) logger.info( icon=f"{self.ICON} 📄", title=f"Upgrading chart from '{chart}': ", end="", ) replica_timeout_multiplier = 2 if settings.K8S_REPLICACOUNT > 1 else 1 timeout = ( (settings.K8S_PROBE_INITIAL_DELAY * replica_timeout_multiplier) + (settings.K8S_PROBE_FAILURE_THRESHOLD * settings.K8S_PROBE_PERIOD) + 120 # Buffer time ) # Construct initial helm upgrade command install_arg = "--install" if install else "" helm_command = [ "helm", "upgrade", "--atomic", "--timeout", f"{timeout}s", "--history-max", "30", install_arg, "--namespace", f"{namespace}", ] if version: helm_command += ["--version", version] # Add values files if values_files: helm_command += self.get_chart_params(flag="--values", values=values_files) safe_name = kubernetes_safe_name(name=name) values_yaml = yaml.dump(values) with NamedTemporaryFile(buffering=0) as fobj: fobj.write(values_yaml.encode()) result = run_os_command( [*helm_command, "--values", fobj.name, f"{safe_name}", f"{chart}"], ) if result.return_code: logger.std(result, raise_exception=raise_exception) return result logger.success() logger.info(f"\tName: {safe_name} (orig: {name})") logger.info(f"\tNamespace: {namespace}") return result