From 43eff8bc92611d706ffd5399a466d6bc98a76ed2 Mon Sep 17 00:00:00 2001 From: taleksovska Date: Tue, 30 Apr 2024 12:55:06 +0200 Subject: [PATCH 1/7] Match the PyPi file structure for packages --- src/enabler/__init__.py | 0 src/enabler/commands/__init__.py | 0 src/enabler/commands/cmd_apps.py | 70 ++++ src/enabler/commands/cmd_kind.py | 275 +++++++++++++ src/enabler/commands/cmd_platform.py | 241 +++++++++++ src/enabler/commands/cmd_preflight.py | 112 ++++++ src/enabler/commands/cmd_setup.py | 439 +++++++++++++++++++++ src/enabler/commands/cmd_version.py | 13 + src/enabler/commands/enabler_completion.sh | 30 ++ src/enabler/dependencies.yaml | 5 + src/enabler/enabler.py | 67 ++++ src/enabler/grafana-vs.yaml | 36 ++ src/enabler/helpers/__init__.py | 0 src/enabler/helpers/git.py | 28 ++ src/enabler/helpers/kind.py | 20 + src/enabler/helpers/kube.py | 37 ++ src/enabler/type/__init__.py | 0 src/enabler/type/semver.py | 20 + 18 files changed, 1393 insertions(+) create mode 100644 src/enabler/__init__.py create mode 100644 src/enabler/commands/__init__.py create mode 100644 src/enabler/commands/cmd_apps.py create mode 100644 src/enabler/commands/cmd_kind.py create mode 100644 src/enabler/commands/cmd_platform.py create mode 100644 src/enabler/commands/cmd_preflight.py create mode 100755 src/enabler/commands/cmd_setup.py create mode 100644 src/enabler/commands/cmd_version.py create mode 100755 src/enabler/commands/enabler_completion.sh create mode 100644 src/enabler/dependencies.yaml create mode 100644 src/enabler/enabler.py create mode 100644 src/enabler/grafana-vs.yaml create mode 100644 src/enabler/helpers/__init__.py create mode 100644 src/enabler/helpers/git.py create mode 100644 src/enabler/helpers/kind.py create mode 100644 src/enabler/helpers/kube.py create mode 100644 src/enabler/type/__init__.py create mode 100644 src/enabler/type/semver.py diff --git a/src/enabler/__init__.py b/src/enabler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/enabler/commands/__init__.py b/src/enabler/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/enabler/commands/cmd_apps.py b/src/enabler/commands/cmd_apps.py new file mode 100644 index 0000000..6c7768b --- /dev/null +++ b/src/enabler/commands/cmd_apps.py @@ -0,0 +1,70 @@ +from src.enabler.enabler import pass_environment, logger + +import click +import subprocess as s + + +# App group of commands +@click.group('apps', short_help='App commands') +@click.pass_context +@pass_environment +def cli(ctx, kube_context_cli): + """Application specific commands such as creation of kubernetes + objects such as namespaces, configmaps etc. The name of the + context is taken from the option --kube-context + which defaults to 'keitaro'""" + pass + + +# Namespace setup +@cli.command('namespace', short_help='Create namespace') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.argument('name', + required=True) +@click.pass_context +@pass_environment +def ns(ctx, kube_context_cli, kube_context, name): + """Create a namespace with auto-injection""" + + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + + # Create a namespace in kubernetes + ns_exists = s.run(['kubectl', + 'get', + 'ns', + name, + '--context', + 'kind-' + kube_context], + capture_output=True) + if ns_exists.returncode != 0: + try: + app_ns = s.run(['kubectl', # noqa + 'create', + 'ns', + name, + '--context', + 'kind-' + kube_context], + capture_output=True, check=True) + logger.info('Created a namespace for ' + name) + app_ns_label = s.run(['kubectl', # noqa + 'label', + 'namespace', + name, + 'istio-injection=enabled', + '--context', + 'kind-' + kube_context], + capture_output=True, check=True) + logger.info('Labeled ' + name + ' namespace for istio injection') + except s.CalledProcessError as error: + logger.error('Something went wrong with namespace: ' + + error.stderr.decode('utf-8')) + raise click.Abort() + else: + logger.info('Skipping creation of ' + name + ' namespace ' + 'since it already exists.') diff --git a/src/enabler/commands/cmd_kind.py b/src/enabler/commands/cmd_kind.py new file mode 100644 index 0000000..5335087 --- /dev/null +++ b/src/enabler/commands/cmd_kind.py @@ -0,0 +1,275 @@ +from src.enabler.enabler import pass_environment, logger +from enabler.helpers import kind, kube + +import click +import click_spinner +import subprocess as s +import docker +import os +from time import sleep +import socket + + +# Kind group of commands +@click.group('kind', short_help='Manage kind clusters') +@click.pass_context +@pass_environment +def cli(ctx, kube_context_cli): + """Manage kind clusters. + The name of the cluster is taken from the option --kube-context + """ + pass + + +@cli.command('create', short_help='Create cluster') +@click.argument('configfile', + type=click.Path(exists=True), + default='kind-cluster.yaml') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.pass_context +@pass_environment +def create(ctx, kube_context_cli, kube_context, configfile): + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + + logger.error("--kube-context was not specified") + raise click.Abort() + # Check if config file exists + base_name, extension = os.path.splitext(configfile) + if not os.path.exists(configfile) or extension != '.yaml': + logger.error('Config file not found.') + raise click.Abort() + kind_configfile_validation(configfile) + + # Check if kind cluster is already created + if kind.kind_get(kube_context): + logger.error('Kind cluster \'' + kube_context + '\' already exists') + raise click.Abort() + try: + logger.debug('Running: `kind create cluster`') + create_cluster = s.run(['kind', # noqa + 'create', + 'cluster', + '--name', + kube_context, + '--config', + click.format_filename(configfile)], + capture_output=False, check=True) + except s.CalledProcessError as error: + logger.critical('Could not create kind cluster: ' + str(error)) + + +@cli.command('delete', short_help='Delete cluster') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.pass_context +@pass_environment +def delete(ctx, kube_context_cli, kube_context): + """Delete a kind cluster""" + # Check if the kind cluster exists + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + if not kind.kind_get(kube_context): + logger.error('Kind cluster \'' + kube_context + '\' doesn\'t exist') + raise click.Abort() + + # Delete the kind cluster + try: + logger.debug('Running: `kind delete cluster`') + create_cluster = s.run(['kind', # noqa + 'delete', + 'cluster', + '--name', + kube_context], + capture_output=False, check=True) + except s.CalledProcessError as error: + logger.critical('Could not delete kind cluster:' + str(error)) + + +@cli.command('status', short_help='Cluster status') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.pass_context +def status(ctx, kube_context): + """Check the status of the kind cluster""" + if kube_context is not None: + if kind.kind_get(kube_context): + if kube.kubectl_info(kube_context): + logger.info('Kind cluster \'' + kube_context + '\' is running') + else: + logger.error('Cluster not running. Please start the cluster') + raise click.Abort() + else: + logger.error('Kind cluster \'' + kube_context + '\' does not exist.') # noqa + else: + logger.error('No kube-context provided.') + + +@cli.command('start', short_help='Start cluster') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.pass_context +@pass_environment +def start(ctx, kube_context_cli, kube_context): + """Start kind cluster""" + + # Kind creates containers with a label io.x-k8s.kind.cluster + # Kind naming is clustername-control-plane and clustername-worker{x} + # The idea is to find the containers check status and ports + # and start them then configure kubectl context + + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + + kind_cp = kube_context + '-control-plane' + kind_workers = kube_context + '-worker' + + # Check if the cluster exists + if kind.kind_get(kube_context): + if kube.kubectl_info(kube_context): + logger.info('Kind cluster \'' + kube_context + '\' is running') + else: + # Check and start kind cluster docker containers + client = docker.from_env() + kind_containers = client.containers.list( + all, filters={'label': 'io.x-k8s.kind.cluster'}) + with click_spinner.spinner(): + for container in kind_containers: + if kind_cp in container.name: + if container.status != 'running': + container.start() + logger.debug('Container ' + + container.name + ' started') + container = client.containers.get(container.id) + # Configure kubeconfig + if kube.kubeconfig_set(container, kube_context): + logger.debug('Reconfigured kubeconfig') + else: + logger.critical('Couldnt configure kubeconfig') + raise click.Abort() + else: + logger.debug('Container ' + container.name + + ' is running') + if kube.kubeconfig_set(container, kube_context): + logger.debug('Reconfigured kubeconfig') + else: + logger.critical('Couldnt configure kubeconfig') + raise click.Abort() + elif kind_workers in container.name: + container.start() + logger.info('Container ' + container.name + ' started') + # It takes a while for the cluster to start + logger.debug('Cluster components started. ' + 'Waiting for cluster to be ready') + tries = 1 + while not kube.kubectl_info(kube_context) and tries < 10: + sleep(30) + tries += 1 + if kube.kubectl_info(kube_context): + logger.debug('Kind cluster ' + kube_context + ' started!') + else: + logger.error('Couldn\'t start kind cluster ' + kube_context) + else: + logger.error('Kind cluster \'' + kube_context + '\' does not exist.') + logger.error('Please create a cluster with "enabler kind create"') + + +@cli.command('stop', short_help='Stop cluster') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.pass_context +@pass_environment +def stop(ctx, kube_context_cli, kube_context): + """Stop kind cluster""" + # Check if the cluster exists + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + + # Kind creates containers with a label io.x-k8s.kind.cluster + # Kind naming is clustername-control-plane and clustername-worker{x} + # The idea is to find the containers and stop them + kind_cp = kube_context + '-control-plane' + kind_workers = kube_context + '-worker' + + if kind.kind_get(kube_context): + # Check and stop kind cluster docker containers + client = docker.from_env() + kind_containers = client.containers() + with click_spinner.spinner(): + for container_info in kind_containers: + container_name = container_info['Names'][0] + if container_name and (kind_cp in container_name or kind_workers in container_name): # noqa + container_state = container_info['State'][0] + if container_state == 'running': + container_info.stop() + logger.debug('Container ' + container_name + ' stopped') # noqa + else: + logger.debug('Container ' + container_name + ' is already stopped') # noqa + logger.info('Kind cluster ' + kube_context + ' was stopped.') + else: + logger.error('Kind cluster \'' + kube_context + '\' does not exist.') + + +# Functions to check if config file has neccessary fields +# and localhost port is free +def kind_configfile_validation(configfile): + """Validates kind-cluster.yaml file""" + + # Get content of configfile + with open(configfile, 'r') as yaml_file: + yaml_content = yaml_file.read() + + keywords_to_check = ['kind', 'apiVersion', 'nodes'] + lines = yaml_content.split('\n') + keywords_in_file = [] + for line in lines: + index = line.find('hostPort:') + if index != -1: + line_content = line.strip().split(" ") + port = line_content[1] + if check_if_port_is_free(int(port)) is not True: + logger.warn("Possible port conflict on hostPort: " + port + + ' in ' + configfile + '.') + pass + + # Check if all key parameters are present and at level 1 + for key in keywords_to_check: + if f'{key}:' in line[0:len(key)+1]: + keywords_in_file.append(key) + + # Get only unique key words that are missing from yaml + difference = list(set(keywords_to_check) - set(keywords_in_file)) + missing_string = ",".join(difference) + + if len(difference) == 1: + logger.warn("Field "+missing_string+" missing in "+configfile+'.') + elif len(difference) >= 2: + logger.warn("Fields "+missing_string+" missing in "+configfile+'.') + + +def check_if_port_is_free(port_number): + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(2) + s.bind(("127.0.0.1", port_number)) + s.listen(1) + except (socket.error, ConnectionRefusedError): + return False + + return True diff --git a/src/enabler/commands/cmd_platform.py b/src/enabler/commands/cmd_platform.py new file mode 100644 index 0000000..632d59b --- /dev/null +++ b/src/enabler/commands/cmd_platform.py @@ -0,0 +1,241 @@ +from src.enabler.enabler import pass_environment, logger +from enabler.helpers.git import get_submodules, get_repo +from enabler.type import semver + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.backends import default_backend +import click +import click_spinner +import git +import os +import subprocess as s + + +# App group of commands +@click.group('platform', short_help='Platform commands') +@click.pass_context +@pass_environment +def cli(ctx, kube_context_cli): + """Platform commands to help with handling the codebase and repo""" + pass + + +@cli.command('init', short_help='Initialize platform components') +@click.argument('submodules', + required=True, + default='all') +@click.argument('repopath', + required=True, + type=click.Path(exists=True), + default=os.getcwd()) +@click.pass_context +@pass_environment +def init(ctx, kube_context_cli, submodules, repopath): + """Init the platform by doing submodule init and checkout + all submodules on master""" + + # Get the repo from arguments defaults to cwd + repo = get_repo(repopath) + + submodules = get_submodules(repo, submodules) + + with click_spinner.spinner(): + for submodule in submodules: + try: + smodule = repo.submodule(submodule) + smodule.update() + logger.info('Fetching latest changes for {}'.format(submodule)) + except Exception as e: + logger.error(f'An error occurred while updating {submodule}: {e}' .format(submodule,e)) # noqa + + logger.info('Platform initialized.') + + +@cli.command('info', short_help='Get info on platform') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.pass_context +@pass_environment +def info(ctx, kube_context_cli, kube_context): + """Get info on platform and platform components""" + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + try: + gw_url = s.run(['kubectl', + '--context', + 'kind-' + kube_context, + '-n', + 'istio-system', + 'get', + 'service', + 'istio-ingressgateway', + '-o', + 'jsonpath={.status.loadBalancer.ingress[0].ip}'], + capture_output=True, check=True) + logger.info('Platform can be accessed through the URL:') + logger.info(u'\u2023' + ' http://' + gw_url.stdout.decode('utf-8')) + kube_info = s.run(['kubectl', 'cluster-info'], capture_output=True, check=True) # noqa + logger.info(kube_info.stdout.decode('utf-8')) + except s.CalledProcessError as error: + logger.error(error.stderr.decode('utf-8')) + raise click.Abort() + + +# Generate keys +@cli.command('keys', short_help='Generate keys') +@click.argument('bits', + required=True, + default=2048) +@click.pass_context +@pass_environment +def keys(ctx, kube_context_cli, bits): + """Generate encryption keys used by the application services""" + # Locations, we can argument these if need be + keys_dir = 'infrastructure/keys/' + private_key_filename = 'key.pem' + public_key_filename = 'key.pub' + + # Check if keys directory exists + if not os.path.exists(keys_dir): + logger.info("Creating key directory...") + os.makedirs(keys_dir) + logger.info("Keys directory already exists") + + # Check if the keys exist and warn user + if ( + os.path.isfile(keys_dir + private_key_filename) or + os.path.isfile(keys_dir + public_key_filename) + ): + if not click.confirm('Keys already exist, overwrite y/n?'): + raise click.Abort() + + # Generate the keys using cryptography + logger.info('Generating keys...') + with click_spinner.spinner(): + key = rsa.generate_private_key( + backend=default_backend(), + public_exponent=65537, + key_size=bits + ) + private_key = key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption()) + public_key = key.public_key().public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + # Write the private key + f = open(keys_dir + private_key_filename, 'wb') + f.write(private_key) + f.close() + + # Write the public key + f = open(keys_dir + public_key_filename, 'wb') + f.write(public_key) + f.close() + logger.info('Keys generated successfully.') + + +@cli.command('release', short_help='Make a platform release') +@click.argument('version', type=semver.BasedVersionParamType(), required=True) +@click.argument('submodule_path', required=True, type=click.Path(exists=True)) +@click.pass_context +@pass_environment +def release(ctx, kube_context_cli, version, submodule_path): + """Release platform by tagging platform repo and + tagging the individual component (git submodule) + using its respective SHA that the submodule points at""" + submodule_name = os.path.basename(submodule_path) + + # Get the repository + repo = get_repo(os.getcwd()) + if not repo: + click.echo("Repository not found.") + return + + # Check if submodule exists in the repository + submodule = next((s for s in repo.submodules if s.name.endswith(submodule_name)), None) # noqa + if not submodule: + click.echo(f"Submodule '{submodule_name}' not found in the repository.") # noqa + return + + # Tag platform at provided version + platform_tag_name = f"v{version}" + tag_result = tag_repo(repo, platform_tag_name) + + if tag_result: + click.echo(f"Platform version: {platform_tag_name}") + else: + click.echo("Failed to tag platform") + + submodule_path = os.path.join(repo.working_dir, submodule_path) + submodule_repo = git.Repo(submodule_path) + submodule_sha = submodule_repo.head.commit.hexsha + submodule_tag_name = f"{submodule_name}-{platform_tag_name}" + tag_result = tag_repo(submodule_repo, submodule_tag_name, submodule_sha) + if tag_result: + click.echo(f"{submodule_name} version: {platform_tag_name}") + else: + click.echo(f"Failed to tag {submodule_name} at {submodule_sha}") + + +def tag_repo(repo, tag_name, commit_sha=None): + try: + if commit_sha: + repo.create_tag(tag_name, ref=commit_sha) + else: + repo.create_tag(tag_name) + return True + except git.GitCommandError as e: + if "already exists" in str(e): + return True # Tag already exists + logger.error(f"Error tagging repository: {e}") + return False + + +@cli.command('version', short_help='Get all versions of components') +@click.argument('submodules', + required=True, + default='all') +@click.argument('repopath', + required=True, + type=click.Path(exists=True), + default=os.getcwd()) +@click.pass_context +@pass_environment +def version(ctx, kube_context_cli, submodules, repopath): + """Check versions of microservices in git submodules + You can provide a comma-separated list of submodules + or you can use 'all' for all submodules""" + + # Get the repo from arguments defaults to cwd + try: + repo = get_repo(repopath) + logger.info("REPO") + logger.info(repo) + submodules = get_submodules(repo, submodules) + except Exception as e: # noqa + logger.info("An error occurred while getting submodule") + + version_info = [] + # Retrieve version information for each submodule + for submodule in submodules: + submodule_path = os.path.join(repo.working_dir, submodule) + try: + smrepo = git.Repo(submodule_path) + tags = smrepo.tags + # Choose the latest tag as version + latest_tag = max(tags, key=lambda t: t.commit.committed_datetime) + version_info.append((submodule, latest_tag.name)) + except Exception as e: # noqa + version_info.append((submodule, "Error retrieving version")) + + for submodule, version in version_info: + logger.info(f"{submodule}: {version}") diff --git a/src/enabler/commands/cmd_preflight.py b/src/enabler/commands/cmd_preflight.py new file mode 100644 index 0000000..a5a7a69 --- /dev/null +++ b/src/enabler/commands/cmd_preflight.py @@ -0,0 +1,112 @@ +from src.enabler.enabler import pass_environment, logger + +import click +import subprocess as s + + +@click.command('preflight', short_help='Preflight checks') +@click.pass_context +@pass_environment +def cli(ctx, kube_context_cli): + """Preflight checks to ensure all tools and versions are present""" + # Check java + try: + java_ver = s.run(['java', '-version'], + capture_output=True, check=True) + # Check that we have java 11 + java_major_ver = java_ver.stderr.decode('utf-8').split()[2].strip('"') + if java_major_ver[:2] != '11': + logger.error( + 'Java JDK 11 needed, please change the version of java on your system') # noqa + else: + logger.info('✓ java jdk 11') + logger.debug(java_ver.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('java not found in PATH.') + except s.CalledProcessError as error: + logger.critical('java -version returned something unexpected: ' + + error.stderr.decode('utf-8')) + + # Check docker + try: + docker_ps = s.run(['docker', 'ps'], + capture_output=True, check=True) + logger.info('✓ docker') + logger.debug(docker_ps.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('docker not found in PATH.') + except s.CalledProcessError as error: + logger.critical('`docker ps` returned something unexpected: ' + + error.stderr.decode('utf-8')) + logger.critical('Please ensure the docker daemon is running and that ' + 'your user is part of the docker group. See README') + + # Check helm 3 + try: + helm_ver = s.run(['helm', 'version', '--short'], + capture_output=True, check=True) + # Check that we have helm 3 + if helm_ver.stdout.decode('utf-8')[1] != "3": + logger.error( + 'Old version of helm detected when running "helm" from PATH.') + else: + logger.info('✓ helm 3') + logger.debug(helm_ver.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('helm not found in PATH.') + except s.CalledProcessError as error: + logger.critical('helm version returned something unexpected: ' + + error.stderr.decode('utf-8')) + + # Check kind + try: + kind_ver = s.run(['kind', 'version'], + capture_output=True, check=True) + logger.info('✓ kind') + logger.debug(kind_ver.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('kind not found in PATH.') + except s.CalledProcessError as error: + logger.critical('kind version returned something unexpected: ' + + error.stderr.decode('utf-8')) + + # Check skaffold + try: + skaffold_ver = s.run(['skaffold', 'version'], + capture_output=True, check=True) + logger.info('✓ skaffold') + logger.debug(skaffold_ver.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('skaffold not found in PATH.') + except s.CalledProcessError as error: + logger.critical('skaffold version returned something unexpected: ' + + error.stderr.decode('utf-8')) + + # Check kubectl + try: + kubectl_ver = s.run(['kubectl', 'version', '--client=true'], + capture_output=True, check=True) + logger.info('✓ kubectl') + logger.debug(kubectl_ver.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('kubectl not found in PATH.') + except s.CalledProcessError as error: + logger.critical('kubectl version returned something unexpected: ' + + error.stderr.decode('utf-8')) + + # Check istioctl + try: + istioctl_ver = s.run(['istioctl', 'version', '-s', '--remote=false'], + capture_output=True, check=True) + # Check that we have istio 1.5 or higher + if istioctl_ver.stdout.decode('utf-8')[2] < "5": + logger.error( + 'Old version of istio detected when running "istioctl" from PATH.') # noqa + else: + logger.info('✓ istioctl') + logger.debug(istioctl_ver.stdout.decode('utf-8')) + except FileNotFoundError: + logger.critical('istioctl not found in PATH.') + except s.CalledProcessError as error: + logger.critical('istioctl version returned something unexpected: ' + + error.stderr.decode('utf-8')) diff --git a/src/enabler/commands/cmd_setup.py b/src/enabler/commands/cmd_setup.py new file mode 100755 index 0000000..73a5428 --- /dev/null +++ b/src/enabler/commands/cmd_setup.py @@ -0,0 +1,439 @@ +from src.enabler.enabler import pass_environment, logger + +import click +import click_spinner +import subprocess as s +import docker +import ipaddress +import urllib.request +import os +import stat +import tarfile +import yaml +import requests +import semver +import re + + +# Setup group of commands +@click.group('setup', short_help='Setup infrastructure services') +@click.pass_context +@pass_environment +def cli(ctx, kube_context_cli): + """Setup infrastructure services on kubernetes. + The name of the context is taken from the option --kube-context + which defaults to 'keitaro'""" + pass + + +# Fetch all binaries for the dependencies +@cli.command('init', short_help='Initialize dependencies') +@click.pass_context +@pass_environment +def init(ctx, kube_context_cli): + """Download binaries for all dependencies""" + + # Check if bin folder exists + check_bin_folder() + + # Figure out what kind of OS are we on + ostype = os.uname().sysname.lower() + + # Load URLs from the JSON file + enabler_path = get_path() + logger.info("Enabler path") + logger.info(enabler_path) + file_path = os.path.join(enabler_path, 'dependencies.yaml') + with open(file_path, 'r') as f: + urls = yaml.safe_load(f) + + # Use the URLs + kubectl_url = urls["kubectl"].format(ostype) + helm_url = urls["helm"].format(ostype) + istioctl_url = urls["istioctl"].format(ostype) + kind_url = urls["kind"].format(ostype) + skaffold_url = urls["skaffold"].format(ostype) + + with click_spinner.spinner(): + # Download kubectl if not exists + kubectl_location = 'bin/kubectl' + if os.path.exists(kubectl_location): + logger.info(f'kubectl already exists at: {kubectl_location}') + else: + logger.info('Downloading kubectl...') + download_and_make_executable(kubectl_url, kubectl_location) + + # Download helm if not exists or update if necessary + helm_location = 'bin/helm' + if os.path.exists(helm_location): + logger.info(f'helm already exists at: {helm_location}') + update_binary_if_necessary(helm_location, helm_url, ostype) + else: + logger.info('Downloading helm...') + download_and_make_executable(helm_url, helm_location) + + # Download istioctl if not exists or update if necessary + istioctl_location = 'bin/istioctl' + if os.path.exists(istioctl_location): + logger.info(f'istioctl already exists at: {istioctl_location}') + update_binary_if_necessary(istioctl_location, istioctl_url, ostype) + else: + logger.info('Downloading istioctl...') + download_and_make_executable(istioctl_url, istioctl_location) + + # Download kind if not exists or update if necessary + kind_location = 'bin/kind' + if os.path.exists(kind_location): + logger.info(f'kind already exists at: {kind_location}') + update_binary_if_necessary(kind_location, kind_url, ostype) + else: + logger.info('Downloading kind...') + download_and_make_executable(kind_url, kind_location) + + # Download skaffold if not exists + skaffold_location = 'bin/skaffold' + if os.path.exists(skaffold_location): + logger.info(f'skaffold already exists at: {skaffold_location}') + else: + logger.info('Downloading skaffold...') + download_and_make_executable(skaffold_url, skaffold_location) + + logger.info('All dependencies downloaded to bin/') + logger.info('IMPORTANT: Please add the path to your user profile to ' + + os.getcwd() + '/bin directory at the beginning of your PATH') + logger.info('$ echo export PATH=' + os.getcwd() + '/bin:$PATH >> ~/.profile') # noqa + logger.info('$ source ~/.profile') + + +def get_latest_version_from_github(repo_url, ostype): + try: + # Fetch the GitHub releases page + response = requests.get(repo_url) + response.raise_for_status() + latest_version = response.json()["tag_name"] + if latest_version: + return latest_version + else: + logger.error("Failed to find latest release tag") + return None + except requests.exceptions.RequestException as e: + logger.error(f"Error fetching latest version from GitHub: {e}") + return None + + +def get_current_version(binary_location, binary_name): + if os.path.exists(binary_location): + filename = os.path.basename(binary_location) + version = extract_version_from_filename(filename, binary_name) + return version + else: + logger.error(f"Binary {binary_name} not found at location: {binary_location}") # noqa + return None + + +def extract_version_from_filename(filename, binary_name): + if binary_name in filename: + # Check if the filename contains the binary_name + if binary_name == "helm" or binary_name == "istioctl": + match = re.search(r'{}-v(\d+\.\d+\.\d+)'.format(binary_name), filename) # noqa + if match: + return match.group(1) + elif binary_name == "kind": + match = re.search(r'{}-(\d+\.\d+\.\d+)'.format(binary_name), filename) # noqa + if match: + return match.group(1) + return None + + +def download_and_make_executable(url, destination): + urllib.request.urlretrieve(url, destination) + st = os.stat(destination) + os.chmod(destination, st.st_mode | stat.S_IEXEC) + logger.info(f'{os.path.basename(destination)} downloaded and made executable!') # noqa + + +def update_binary_if_necessary(binary_location, binary_url, ostype): + current_version = get_current_version(binary_location, os.path.basename(binary_url)) # noqa + latest_version = get_latest_version_from_github(binary_url, ostype) + if latest_version is not None: + if semver.compare(current_version, latest_version) < 0: + logger.info(f"Updating {os.path.basename(binary_location)}...") + urllib.request.urlretrieve(binary_url, f'{binary_location}.tar.gz') + tar = tarfile.open(f'{binary_location}.tar.gz', 'r:gz') + for member in tar.getmembers(): + if member.isreg(): + member.name = os.path.basename(member.name) + tar.extract(member, os.path.dirname(binary_location)) + tar.close() + os.remove(f'{binary_location}.tar.gz') + logger.info(f'{os.path.basename(binary_location)} updated!') + + +# Metallb setup +@cli.command('metallb', short_help='Setup metallb') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.option('--ip-addresspool', + help='IP Address range to be assigned to metallb, default is last 10 addresses from kind network.' # noqa + 'The IP address range should be in the kind network in order for the application to work properly.', # noqa + required=False) +@click.option('--version', + help='Version of metallb from bitnami. Default version is 4.6.0', + default='4.6.0') +@click.pass_context +@pass_environment +def metallb(ctx, kube_context_cli, kube_context, ip_addresspool, version): + """Install and setup metallb on k8s""" + # Check if metallb is installed + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + + try: + metallb_exists = s.run(['helm', + 'status', + 'metallb', + '-n', + 'metallb', + '--kube-context', + 'kind-' + kube_context], + capture_output=True, check=True) + logger.info('Metallb is already installed, exiting...') + logger.debug(metallb_exists.stdout.decode('utf-8')) + raise click.Abort() + except s.CalledProcessError: + logger.info('Metallb not found. Installing...') + pass + try: + metallb_repo_add = s.run(['helm', + 'repo', + 'add', + 'bitnami', + 'https://charts.bitnami.com/bitnami'], + capture_output=True, check=True) + logger.info('Downloading metallb version ...') + logger.debug(metallb_repo_add.stdout.decode('utf-8')) + metallb_repo_add = s.run(['helm', + 'repo', + 'update'], + capture_output=True, check=True) + logger.info('Repo update ...') + logger.debug(metallb_repo_add.stdout.decode('utf-8')) + + except s.CalledProcessError as error: + logger.info('Metallb repo not found.') + logger.error(error.stdout.decode('utf-8')) + raise click.Abort() + + # Get the Subnet of the kind network + client = docker.from_env() + networks = client.networks() + + # Find the network with name 'kind' + kind_network = None # Initialize kind_network outside the loop + for network in networks: + if network['Name'] == 'kind': + kind_network = network + break + + if kind_network is not None: + # Do something with kind_network + logger.info("Kind network found:", kind_network) + kind_subnet = kind_network['IPAM']['Config'][0]['Subnet'] + else: + logger.info("Kind network not found.") + + # Extract the last 10 ip addresses of the kind subnet + ips = [str(ip) for ip in ipaddress.IPv4Network(kind_subnet)] + metallb_ips = ips[-10:] + + if ip_addresspool is None: + ip_addresspool = metallb_ips[0] + ' - ' + metallb_ips[-1] + else: + # Check if ip address range is in kind network and print out a warning + ip_range = ip_addresspool.strip().split('-') + try: + start_ip = ipaddress.IPv4Address(ip_range[0]) + end_ip = ipaddress.IPv4Address(ip_range[1]) + except Exception: + logger.error('Incorrect IP address range: ' + ip_addresspool) + + if start_ip not in ipaddress.IPv4Network(kind_subnet) or end_ip not in ipaddress.IPv4Network(kind_subnet): # noqa + logger.error('Provided IP address range not in kind network.') + logger.error('Kind subnet is: ' + kind_subnet) + raise click.Abort() + + # Check if version is 3.x.x and then use config map to install, else if version 4.x.x use CDR file for installing. # noqa + # And dynamically set the IP Address range in the compatible .yaml file + if version.split('.')[0] == '3': + yaml_file_path = 'enabler/metallb-configmap.yaml' + with open(yaml_file_path, 'r') as yaml_file: + config = yaml.safe_load(yaml_file) + modified_string = config['data']['config'][:-32]+ip_addresspool+"\n" + config['data']['config'] = modified_string + + logger.info('Metallb will be configured in Layer 2 mode with the range: ' + ip_addresspool) # noqa + updated_yaml = yaml.dump(config, default_flow_style=False) + with open(yaml_file_path, 'w') as yaml_file: + yaml_file.write(updated_yaml) + + elif int(version.split('.')[0]) >= 4: + yaml_file_path = 'metallb-crd.yaml' + with open(yaml_file_path, 'r') as yaml_file: + config = list(yaml.safe_load_all(yaml_file)) + + + logger.info('Metallb will be configured in Layer 2 mode with the range: ' + ip_addresspool) # noqa + for doc in config: + if 'kind' in doc and doc['kind'] == 'IPAddressPool': + doc['spec']['addresses'] = [ip_addresspool] + + with open(yaml_file_path, 'w') as yaml_file: + yaml.dump_all(config, yaml_file, default_flow_style=False) + else: + logger.info('Incompatible format for Metallb version. Please check official versions ') # noqa + ns_exists = s.run(['kubectl', + 'get', + 'ns', + 'metallb', + '--context', + 'kind-' + kube_context], + capture_output=True) + if ns_exists.returncode != 0: + try: + metallb_ns = s.run(['kubectl', # noqa + 'create', + 'ns', + 'metallb', + '--context', + 'kind-' + kube_context], + capture_output=True, check=True) + logger.info('Created a namespace for metallb') + except s.CalledProcessError as error: + logger.error('Could not create namespace for metallb: ' + + error.stderr.decode('utf-8')) + raise click.Abort() + else: + logger.info('Skipping creation of metallb namespace ' + 'since it already exists.') + # Install metallb on the cluster + try: + helm_metallb = s.run(['helm', + 'install', + 'metallb', + '--kube-context', + 'kind-' + kube_context, + '--version', + version, + 'bitnami/metallb', + '-n', + 'metallb', + '--wait'], + capture_output=True, check=True) + + # Apply configuration from CRD file + config_metallb = s.run(['kubectl', # noqa + 'apply', + '-f', + yaml_file_path], capture_output=True, check=True) # noqa + + logger.info('✓ Metallb installed on cluster.') + logger.debug(helm_metallb.stdout.decode("utf-8")) + except s.CalledProcessError as error: + logger.error('Could not install metallb') + logger.error(error.stderr.decode('utf-8')) + + +# Istio setup +@cli.command('istio', short_help='Setup Istio') +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click.argument('monitoring_tools', + required=False + ) +@click.pass_context +@pass_environment +def istio(ctx, kube_context_cli, kube_context, monitoring_tools): + """Install and setup istio on k8s""" + if ctx.kube_context is not None: + kube_context = ctx.kube_context + if ctx.kube_context is None and kube_context is None: + logger.error("--kube-context was not specified") + raise click.Abort() + + # Run verify install to check whether we are ready to install istio + try: + istio_verify = s.run(['istioctl', + 'verify-install', + '--context', + 'kind-' + kube_context], + capture_output=True, check=True) + logger.info('Istio pre-check passed. Proceeding with install') + logger.info(istio_verify.stderr.decode('utf-8')) + except s.CalledProcessError as error: + logger.critical('Istio pre-check failed') + logger.critical(error.stderr.decode('utf-8')) + raise click.Abort() + + # Install Istio + logger.info('Installing istio, please wait...') + with click_spinner.spinner(): + istio_command = ['istioctl', + 'manifest', + 'apply', + '-y', + '--set', + 'profile=default'] + if monitoring_tools == 'monitoring-tools': + monitoring_config = ['--set', + 'addonComponents.grafana.enabled=true', + '--set', + 'addonComponents.kiali.enabled=true', + '--set', + 'addonComponents.prometheus.enabled=true', + '--set', + 'addonComponents.tracing.enabled=true', + '--set', + 'values.kiali.dashboard.jaegerURL=http://jaeger-query:16686', # noqa + '--set', + 'values.kiali.dashboard.grafanaURL=http://grafana:3000'] # noqa + + istio_command.extend(monitoring_config) + istio_command.append('--context') + istio_command.append('kind-' + kube_context) + istio_command.append('--wait') + try: + istio_install = s.run(istio_command, + capture_output=True, check=True) + logger.info('Istio installed') + logger.debug(istio_install.stdout.decode('utf-8')) + except s.CalledProcessError as error: + logger.critical('Istio installation failed') + logger.critical(error.stderr.decode('utf-8')) + raise click.Abort() + if monitoring_tools == 'monitoring-tools': + try: + grafana_virtual_service = s.run(['kubectl', 'apply', '-f', 'enabler/grafana-vs.yaml'], capture_output=True, check=True) # noqa + except Exception as e: + logger.error('Error setting grafana URL') + logger.error(str(e)) + + +def get_path(): + enabler_path = os.getcwd() + return enabler_path + + +def check_bin_folder(): + enabler_path = os.getcwd() + full_path = enabler_path + '/bin' + if not os.path.exists(full_path): + logger.info("Creating bin folder...") + os.makedirs(full_path) + else: + logger.info("Bin folder already exists. Continue...") + pass diff --git a/src/enabler/commands/cmd_version.py b/src/enabler/commands/cmd_version.py new file mode 100644 index 0000000..3f975eb --- /dev/null +++ b/src/enabler/commands/cmd_version.py @@ -0,0 +1,13 @@ +from src.enabler.enabler import pass_environment, logger +import pkg_resources +import click + +# Command to get the current Enabler version +@click.group('version', short_help='Get current version of Enabler', invoke_without_command=True) # noqa +@click.pass_context +@pass_environment +def enabler_version(ctx, kube_context_cli): + """Get current version of Enabler""" + distribution = pkg_resources.get_distribution("enabler") + version = distribution.version + logger.info("Enabler "+version) diff --git a/src/enabler/commands/enabler_completion.sh b/src/enabler/commands/enabler_completion.sh new file mode 100755 index 0000000..80853f8 --- /dev/null +++ b/src/enabler/commands/enabler_completion.sh @@ -0,0 +1,30 @@ +_enabler_complete() { + local cur_word prev_word + + # Get the current and previous words + cur_word="${COMP_WORDS[COMP_CWORD]}" + prev_word="${COMP_WORDS[COMP_CWORD-1]}" + + case "$prev_word" in + "enabler") # noqa + COMPREPLY=( $(compgen -W "apps kind preflight platform setup version" -- "$cur_word") ) + ;; + "apps") + COMPREPLY=( $(compgen -W "namespace" -- "$cur_word") ) + ;; + "platform") + COMPREPLY=( $(compgen -W "init info keys release version" -- "$cur_word") ) + ;; + "kind") + COMPREPLY=( $(compgen -W "create delete status start stop" -- "$cur_word") ) + ;; + "setup") + COMPREPLY=( $(compgen -W "init metallb istio" -- "$cur_word") ) + ;; + *) + COMPREPLY=() + ;; + esac +} + +complete -F _enabler_complete enabler \ No newline at end of file diff --git a/src/enabler/dependencies.yaml b/src/enabler/dependencies.yaml new file mode 100644 index 0000000..8cb31aa --- /dev/null +++ b/src/enabler/dependencies.yaml @@ -0,0 +1,5 @@ +kubectl: "https://storage.googleapis.com/kubernetes-release/release/latest/bin/{}/amd64/kubectl" # noqa +helm: "https://get.helm.sh/helm-v3.1.2-{}-amd64.tar.gz" # noqa +istioctl: "https://github.com/istio/istio/releases/download/1.5.1/istioctl-1.5.1-{}.tar.gz" # noqa +kind: "https://github.com/kubernetes-sigs/kind/releases/download/v0.22.0/kind-{}-amd64" # noqa +skaffold: "https://storage.googleapis.com/skaffold/releases/latest/skaffold-{}-amd64" # noqa \ No newline at end of file diff --git a/src/enabler/enabler.py b/src/enabler/enabler.py new file mode 100644 index 0000000..d072280 --- /dev/null +++ b/src/enabler/enabler.py @@ -0,0 +1,67 @@ +import os +import logging +import click +import click_log +import subprocess + + +class CLI: + def __init__(self, runner): + self.runner = runner + + def version_command(self): + try: + result = subprocess.run(['enabler', 'version'], capture_output=True, text=True, check=True) # noqa + return result.stdout.strip() + except subprocess.CalledProcessError as e: + raise RuntimeError(f"Failed to get version: {e}") + + +CONTEXT_SETTINGS = dict(auto_envvar_prefix="ENABLER") + + +class Environment(object): + def __init__(self): + self.verbose = False + self.home = os.getcwd() + self.kube_context = '' + + +pass_environment = click.make_pass_decorator(Environment, ensure=True) +cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "commands")) # noqa + +# Use logging for nicer handling of log output +logger = logging.getLogger(__name__) +click_log.basic_config(logger) + + +class EnablerCLI(click.MultiCommand): + def list_commands(self, ctx): + rv = [] + for filename in os.listdir(cmd_folder): + if filename.endswith(".py") and filename.startswith("cmd_"): + rv.append(filename[4:-3]) + rv.sort() + return rv + + def get_command(self, ctx, name): + try: + mod = __import__( + "src.enabler.commands.cmd_{}".format(name), None, None, ["cli"] + ) + except ImportError: + return + return mod.cli + + +@click.command(cls=EnablerCLI, context_settings=CONTEXT_SETTINGS) +@click.option('--kube-context', + help='The kubernetes context to use', + required=False) +@click_log.simple_verbosity_option(logger) +@pass_environment +def cli(ctx, kube_context): + """Enabler CLI for ease of setup of microservice based apps""" + + ctx.kube_context = kube_context + logger.debug('Using kube-context: kind-' + str(kube_context)) diff --git a/src/enabler/grafana-vs.yaml b/src/enabler/grafana-vs.yaml new file mode 100644 index 0000000..0c40755 --- /dev/null +++ b/src/enabler/grafana-vs.yaml @@ -0,0 +1,36 @@ +apiVersion: networking.istio.io/v1beta1 +kind: Gateway +metadata: + name: grafana-gateway + namespace: istio-system +spec: + selector: + app: istio-ingressgateway + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + tls: + httpsRedirect: false + hosts: + - "grafana.local" + +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: grafana-vs + namespace: istio-system +spec: + hosts: + - "grafana.local" + gateways: + - grafana-gateway + http: + - route: + - destination: + port: + number: 3000 + host: grafana diff --git a/src/enabler/helpers/__init__.py b/src/enabler/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/enabler/helpers/git.py b/src/enabler/helpers/git.py new file mode 100644 index 0000000..f1a5f6f --- /dev/null +++ b/src/enabler/helpers/git.py @@ -0,0 +1,28 @@ +from enabler.enabler import logger +import click +import git +import os + + +def get_repo(repopath): + """Function to get the repository.""" + if not os.path.exists(repopath): + logger.critical('The repo path ' + repopath + ' does not exist') + raise click.Abort() + + try: + return git.Repo(repopath, odbt=git.GitDB, search_parent_directories=True) # noqa + except git.InvalidGitRepositoryError: + logger.critical('The repo path ' + repopath + ' is not a git repo') + raise click.Abort() + + +def get_submodules(repo, submodules): + """Function to get submodules.""" + if submodules == 'all': + submodules = [submodule.name for submodule in repo.submodules] + else: + submodules = submodules.split(',') + logger.debug('The provided submodules are:') + logger.debug(submodules) + return submodules diff --git a/src/enabler/helpers/kind.py b/src/enabler/helpers/kind.py new file mode 100644 index 0000000..99c406c --- /dev/null +++ b/src/enabler/helpers/kind.py @@ -0,0 +1,20 @@ +from src.enabler.enabler import logger + +import click +import subprocess as s + + +def kind_get(cluster): + # Get kind clusters + try: + logger.debug('Running: `kind get clusters`') + result = s.run(['kind', 'get', 'clusters'], + capture_output=True, check=True) + kind_clusters = result.stdout.decode('utf-8').splitlines() + if cluster in kind_clusters: + return True + else: + return False + except s.CalledProcessError as error: + logger.critical(error.stderr.decode('utf-8')) + raise click.Abort() diff --git a/src/enabler/helpers/kube.py b/src/enabler/helpers/kube.py new file mode 100644 index 0000000..933a04f --- /dev/null +++ b/src/enabler/helpers/kube.py @@ -0,0 +1,37 @@ +from enabler.enabler import logger +import subprocess as s + + +def kubectl_info(cluster): + # Get kubectl cluster-info + try: + logger.debug('Running: `kubectl cluster-info`') + result = s.run(['kubectl', + 'cluster-info', + '--context', + 'kind-' + cluster], + capture_output=True, check=True) + logger.debug(result.stdout.decode('utf-8')) + return True + except s.CalledProcessError as error: + logger.debug(error.stderr.decode('utf-8')) + return False + + +def kubeconfig_set(cont, cluster): + # Get mapped control plane port from docker + port = cont.attrs['NetworkSettings']['Ports']['6443/tcp'][0]['HostPort'] + try: + logger.debug('Running: `kubectl config set-cluster`') + result = s.run(['kubectl', + 'config', + 'set-cluster', + 'kind-' + cluster, + '--server', + 'https://127.0.0.1:' + port], + capture_output=True, check=True) + logger.debug(result.stdout.decode('utf-8')) + return True + except s.CalledProcessError as error: + logger.debug(error.stderr.decode('utf-8')) + return False diff --git a/src/enabler/type/__init__.py b/src/enabler/type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/enabler/type/semver.py b/src/enabler/type/semver.py new file mode 100644 index 0000000..617edd3 --- /dev/null +++ b/src/enabler/type/semver.py @@ -0,0 +1,20 @@ +from semver import parse +import click + + +class BasedVersionParamType(click.ParamType): + name = "semver" + + def convert(self, value, param, ctx): + try: + parse(value) + return (value) + except TypeError: + self.fail( + '{value!r} is not a valid version, please use semver', + param, + ctx, + ) + except ValueError: + self.fail(f'{value!r} is not a valid version, please use semver', + param, ctx) From cf0ea645566a0f2233dfeae0ade42abd32c9fda0 Mon Sep 17 00:00:00 2001 From: taleksovska Date: Tue, 30 Apr 2024 13:19:23 +0200 Subject: [PATCH 2/7] Fix lint errors --- src/enabler/commands/cmd_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enabler/commands/cmd_version.py b/src/enabler/commands/cmd_version.py index 3f975eb..fb42963 100644 --- a/src/enabler/commands/cmd_version.py +++ b/src/enabler/commands/cmd_version.py @@ -1,4 +1,4 @@ -from src.enabler.enabler import pass_environment, logger +from src.enabler.enabler import pass_environment, logger import pkg_resources import click From 8319acc3ec272c1ee347eb9d9aeafca2632d4503 Mon Sep 17 00:00:00 2001 From: taleksovska Date: Tue, 30 Apr 2024 16:56:03 +0200 Subject: [PATCH 3/7] Modify enabler structure --- enabler/cli.py | 67 --- enabler/commands/cmd_kind.py | 275 ----------- enabler/commands/cmd_platform.py | 241 ---------- enabler/commands/cmd_setup.py | 437 ------------------ enabler/helpers/git.py | 28 -- enabler/helpers/kube.py | 37 -- setup.py | 4 +- src/enabler/__init__.py | 0 src/enabler/commands/__init__.py | 0 src/enabler/commands/cmd_apps.py | 70 --- src/enabler/commands/cmd_preflight.py | 112 ----- src/enabler/commands/cmd_version.py | 13 - src/enabler/commands/enabler_completion.sh | 30 -- src/enabler/dependencies.yaml | 5 - src/enabler/grafana-vs.yaml | 36 -- src/enabler/helpers/__init__.py | 0 src/enabler/helpers/kind.py | 20 - src/enabler/type/__init__.py | 0 src/enabler/type/semver.py | 20 - .../enabler_keitaro_inc}/__init__.py | 0 .../enabler_keitaro_inc}/commands/__init__.py | 0 .../enabler_keitaro_inc}/commands/cmd_apps.py | 2 +- .../commands/cmd_kind.py | 4 +- .../commands/cmd_platform.py | 6 +- .../commands/cmd_preflight.py | 2 +- .../commands/cmd_setup.py | 4 +- .../commands/cmd_version.py | 2 +- .../commands/enabler_completion.sh | 0 .../enabler_keitaro_inc}/dependencies.yaml | 4 +- .../enabler.py | 2 +- .../enabler_keitaro_inc}/grafana-vs.yaml | 0 .../enabler_keitaro_inc}/helpers/__init__.py | 0 .../helpers/git.py | 2 +- .../enabler_keitaro_inc}/helpers/kind.py | 2 +- .../helpers/kube.py | 2 +- .../enabler_keitaro_inc}/type/__init__.py | 0 .../enabler_keitaro_inc}/type/semver.py | 0 .../unit_tests => tests}/apps_unittests.py | 4 +- .../unit_tests => tests}/kind_unittests.py | 18 +- .../platform_unittests.py | 18 +- .../preflight_unittests.py | 4 +- .../unit_tests => tests}/setup_unittests.py | 16 +- .../unit_tests => tests}/version_unittests.py | 4 +- 43 files changed, 50 insertions(+), 1441 deletions(-) delete mode 100644 enabler/cli.py delete mode 100644 enabler/commands/cmd_kind.py delete mode 100644 enabler/commands/cmd_platform.py delete mode 100755 enabler/commands/cmd_setup.py delete mode 100644 enabler/helpers/git.py delete mode 100644 enabler/helpers/kube.py delete mode 100644 src/enabler/__init__.py delete mode 100644 src/enabler/commands/__init__.py delete mode 100644 src/enabler/commands/cmd_apps.py delete mode 100644 src/enabler/commands/cmd_preflight.py delete mode 100644 src/enabler/commands/cmd_version.py delete mode 100755 src/enabler/commands/enabler_completion.sh delete mode 100644 src/enabler/dependencies.yaml delete mode 100644 src/enabler/grafana-vs.yaml delete mode 100644 src/enabler/helpers/__init__.py delete mode 100644 src/enabler/helpers/kind.py delete mode 100644 src/enabler/type/__init__.py delete mode 100644 src/enabler/type/semver.py rename {enabler => src/enabler_keitaro_inc}/__init__.py (100%) rename {enabler => src/enabler_keitaro_inc}/commands/__init__.py (100%) rename {enabler => src/enabler_keitaro_inc}/commands/cmd_apps.py (97%) rename src/{enabler => enabler_keitaro_inc}/commands/cmd_kind.py (98%) rename src/{enabler => enabler_keitaro_inc}/commands/cmd_platform.py (97%) rename {enabler => src/enabler_keitaro_inc}/commands/cmd_preflight.py (98%) rename src/{enabler => enabler_keitaro_inc}/commands/cmd_setup.py (99%) rename {enabler => src/enabler_keitaro_inc}/commands/cmd_version.py (86%) rename {enabler => src/enabler_keitaro_inc}/commands/enabler_completion.sh (100%) rename {enabler => src/enabler_keitaro_inc}/dependencies.yaml (84%) rename src/{enabler => enabler_keitaro_inc}/enabler.py (95%) rename {enabler => src/enabler_keitaro_inc}/grafana-vs.yaml (100%) rename {enabler => src/enabler_keitaro_inc}/helpers/__init__.py (100%) rename src/{enabler => enabler_keitaro_inc}/helpers/git.py (94%) rename {enabler => src/enabler_keitaro_inc}/helpers/kind.py (91%) rename src/{enabler => enabler_keitaro_inc}/helpers/kube.py (96%) rename {enabler => src/enabler_keitaro_inc}/type/__init__.py (100%) rename {enabler => src/enabler_keitaro_inc}/type/semver.py (100%) rename {enabler/unit_tests => tests}/apps_unittests.py (80%) rename {enabler/unit_tests => tests}/kind_unittests.py (73%) rename {enabler/unit_tests => tests}/platform_unittests.py (78%) rename {enabler/unit_tests => tests}/preflight_unittests.py (85%) rename {enabler/unit_tests => tests}/setup_unittests.py (72%) rename {enabler/unit_tests => tests}/version_unittests.py (79%) diff --git a/enabler/cli.py b/enabler/cli.py deleted file mode 100644 index 7daf21b..0000000 --- a/enabler/cli.py +++ /dev/null @@ -1,67 +0,0 @@ -import os -import logging -import click -import click_log -import subprocess - - -class CLI: - def __init__(self, runner): - self.runner = runner - - def version_command(self): - try: - result = subprocess.run(['enabler', 'version'], capture_output=True, text=True, check=True) # noqa - return result.stdout.strip() - except subprocess.CalledProcessError as e: - raise RuntimeError(f"Failed to get version: {e}") - - -CONTEXT_SETTINGS = dict(auto_envvar_prefix="ENABLER") - - -class Environment(object): - def __init__(self): - self.verbose = False - self.home = os.getcwd() - self.kube_context = '' - - -pass_environment = click.make_pass_decorator(Environment, ensure=True) -cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "commands")) # noqa - -# Use logging for nicer handling of log output -logger = logging.getLogger(__name__) -click_log.basic_config(logger) - - -class EnablerCLI(click.MultiCommand): - def list_commands(self, ctx): - rv = [] - for filename in os.listdir(cmd_folder): - if filename.endswith(".py") and filename.startswith("cmd_"): - rv.append(filename[4:-3]) - rv.sort() - return rv - - def get_command(self, ctx, name): - try: - mod = __import__( - "enabler.commands.cmd_{}".format(name), None, None, ["cli"] - ) - except ImportError: - return - return mod.cli - - -@click.command(cls=EnablerCLI, context_settings=CONTEXT_SETTINGS) -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click_log.simple_verbosity_option(logger) -@pass_environment -def cli(ctx, kube_context): - """Enabler CLI for ease of setup of microservice based apps""" - - ctx.kube_context = kube_context - logger.debug('Using kube-context: kind-' + str(kube_context)) diff --git a/enabler/commands/cmd_kind.py b/enabler/commands/cmd_kind.py deleted file mode 100644 index 0404b70..0000000 --- a/enabler/commands/cmd_kind.py +++ /dev/null @@ -1,275 +0,0 @@ -from enabler.cli import pass_environment, logger -from enabler.helpers import kind, kube - -import click -import click_spinner -import subprocess as s -import docker -import os -from time import sleep -import socket - - -# Kind group of commands -@click.group('kind', short_help='Manage kind clusters') -@click.pass_context -@pass_environment -def cli(ctx, kube_context_cli): - """Manage kind clusters. - The name of the cluster is taken from the option --kube-context - """ - pass - - -@cli.command('create', short_help='Create cluster') -@click.argument('configfile', - type=click.Path(exists=True), - default='kind-cluster.yaml') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.pass_context -@pass_environment -def create(ctx, kube_context_cli, kube_context, configfile): - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - - logger.error("--kube-context was not specified") - raise click.Abort() - # Check if config file exists - base_name, extension = os.path.splitext(configfile) - if not os.path.exists(configfile) or extension != '.yaml': - logger.error('Config file not found.') - raise click.Abort() - kind_configfile_validation(configfile) - - # Check if kind cluster is already created - if kind.kind_get(kube_context): - logger.error('Kind cluster \'' + kube_context + '\' already exists') - raise click.Abort() - try: - logger.debug('Running: `kind create cluster`') - create_cluster = s.run(['kind', # noqa - 'create', - 'cluster', - '--name', - kube_context, - '--config', - click.format_filename(configfile)], - capture_output=False, check=True) - except s.CalledProcessError as error: - logger.critical('Could not create kind cluster: ' + str(error)) - - -@cli.command('delete', short_help='Delete cluster') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.pass_context -@pass_environment -def delete(ctx, kube_context_cli, kube_context): - """Delete a kind cluster""" - # Check if the kind cluster exists - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - if not kind.kind_get(kube_context): - logger.error('Kind cluster \'' + kube_context + '\' doesn\'t exist') - raise click.Abort() - - # Delete the kind cluster - try: - logger.debug('Running: `kind delete cluster`') - create_cluster = s.run(['kind', # noqa - 'delete', - 'cluster', - '--name', - kube_context], - capture_output=False, check=True) - except s.CalledProcessError as error: - logger.critical('Could not delete kind cluster:' + str(error)) - - -@cli.command('status', short_help='Cluster status') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.pass_context -def status(ctx, kube_context): - """Check the status of the kind cluster""" - if kube_context is not None: - if kind.kind_get(kube_context): - if kube.kubectl_info(kube_context): - logger.info('Kind cluster \'' + kube_context + '\' is running') - else: - logger.error('Cluster not running. Please start the cluster') - raise click.Abort() - else: - logger.error('Kind cluster \'' + kube_context + '\' does not exist.') # noqa - else: - logger.error('No kube-context provided.') - - -@cli.command('start', short_help='Start cluster') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.pass_context -@pass_environment -def start(ctx, kube_context_cli, kube_context): - """Start kind cluster""" - - # Kind creates containers with a label io.x-k8s.kind.cluster - # Kind naming is clustername-control-plane and clustername-worker{x} - # The idea is to find the containers check status and ports - # and start them then configure kubectl context - - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - - kind_cp = kube_context + '-control-plane' - kind_workers = kube_context + '-worker' - - # Check if the cluster exists - if kind.kind_get(kube_context): - if kube.kubectl_info(kube_context): - logger.info('Kind cluster \'' + kube_context + '\' is running') - else: - # Check and start kind cluster docker containers - client = docker.from_env() - kind_containers = client.containers.list( - all, filters={'label': 'io.x-k8s.kind.cluster'}) - with click_spinner.spinner(): - for container in kind_containers: - if kind_cp in container.name: - if container.status != 'running': - container.start() - logger.debug('Container ' + - container.name + ' started') - container = client.containers.get(container.id) - # Configure kubeconfig - if kube.kubeconfig_set(container, kube_context): - logger.debug('Reconfigured kubeconfig') - else: - logger.critical('Couldnt configure kubeconfig') - raise click.Abort() - else: - logger.debug('Container ' + container.name + - ' is running') - if kube.kubeconfig_set(container, kube_context): - logger.debug('Reconfigured kubeconfig') - else: - logger.critical('Couldnt configure kubeconfig') - raise click.Abort() - elif kind_workers in container.name: - container.start() - logger.info('Container ' + container.name + ' started') - # It takes a while for the cluster to start - logger.debug('Cluster components started. ' - 'Waiting for cluster to be ready') - tries = 1 - while not kube.kubectl_info(kube_context) and tries < 10: - sleep(30) - tries += 1 - if kube.kubectl_info(kube_context): - logger.debug('Kind cluster ' + kube_context + ' started!') - else: - logger.error('Couldn\'t start kind cluster ' + kube_context) - else: - logger.error('Kind cluster \'' + kube_context + '\' does not exist.') - logger.error('Please create a cluster with "enabler kind create"') - - -@cli.command('stop', short_help='Stop cluster') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.pass_context -@pass_environment -def stop(ctx, kube_context_cli, kube_context): - """Stop kind cluster""" - # Check if the cluster exists - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - - # Kind creates containers with a label io.x-k8s.kind.cluster - # Kind naming is clustername-control-plane and clustername-worker{x} - # The idea is to find the containers and stop them - kind_cp = kube_context + '-control-plane' - kind_workers = kube_context + '-worker' - - if kind.kind_get(kube_context): - # Check and stop kind cluster docker containers - client = docker.from_env() - kind_containers = client.containers() - with click_spinner.spinner(): - for container_info in kind_containers: - container_name = container_info['Names'][0] - if container_name and (kind_cp in container_name or kind_workers in container_name): # noqa - container_state = container_info['State'][0] - if container_state == 'running': - container_info.stop() - logger.debug('Container ' + container_name + ' stopped') # noqa - else: - logger.debug('Container ' + container_name + ' is already stopped') # noqa - logger.info('Kind cluster ' + kube_context + ' was stopped.') - else: - logger.error('Kind cluster \'' + kube_context + '\' does not exist.') - - -# Functions to check if config file has neccessary fields -# and localhost port is free -def kind_configfile_validation(configfile): - """Validates kind-cluster.yaml file""" - - # Get content of configfile - with open(configfile, 'r') as yaml_file: - yaml_content = yaml_file.read() - - keywords_to_check = ['kind', 'apiVersion', 'nodes'] - lines = yaml_content.split('\n') - keywords_in_file = [] - for line in lines: - index = line.find('hostPort:') - if index != -1: - line_content = line.strip().split(" ") - port = line_content[1] - if check_if_port_is_free(int(port)) is not True: - logger.warn("Possible port conflict on hostPort: " + port - + ' in ' + configfile + '.') - pass - - # Check if all key parameters are present and at level 1 - for key in keywords_to_check: - if f'{key}:' in line[0:len(key)+1]: - keywords_in_file.append(key) - - # Get only unique key words that are missing from yaml - difference = list(set(keywords_to_check) - set(keywords_in_file)) - missing_string = ",".join(difference) - - if len(difference) == 1: - logger.warn("Field "+missing_string+" missing in "+configfile+'.') - elif len(difference) >= 2: - logger.warn("Fields "+missing_string+" missing in "+configfile+'.') - - -def check_if_port_is_free(port_number): - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(2) - s.bind(("127.0.0.1", port_number)) - s.listen(1) - except (socket.error, ConnectionRefusedError): - return False - - return True diff --git a/enabler/commands/cmd_platform.py b/enabler/commands/cmd_platform.py deleted file mode 100644 index bf0f580..0000000 --- a/enabler/commands/cmd_platform.py +++ /dev/null @@ -1,241 +0,0 @@ -from enabler.cli import pass_environment, logger -from enabler.helpers.git import get_submodules, get_repo -from enabler.type import semver - -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.backends import default_backend -import click -import click_spinner -import git -import os -import subprocess as s - - -# App group of commands -@click.group('platform', short_help='Platform commands') -@click.pass_context -@pass_environment -def cli(ctx, kube_context_cli): - """Platform commands to help with handling the codebase and repo""" - pass - - -@cli.command('init', short_help='Initialize platform components') -@click.argument('submodules', - required=True, - default='all') -@click.argument('repopath', - required=True, - type=click.Path(exists=True), - default=os.getcwd()) -@click.pass_context -@pass_environment -def init(ctx, kube_context_cli, submodules, repopath): - """Init the platform by doing submodule init and checkout - all submodules on master""" - - # Get the repo from arguments defaults to cwd - repo = get_repo(repopath) - - submodules = get_submodules(repo, submodules) - - with click_spinner.spinner(): - for submodule in submodules: - try: - smodule = repo.submodule(submodule) - smodule.update() - logger.info('Fetching latest changes for {}'.format(submodule)) - except Exception as e: - logger.error(f'An error occurred while updating {submodule}: {e}' .format(submodule,e)) # noqa - - logger.info('Platform initialized.') - - -@cli.command('info', short_help='Get info on platform') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.pass_context -@pass_environment -def info(ctx, kube_context_cli, kube_context): - """Get info on platform and platform components""" - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - try: - gw_url = s.run(['kubectl', - '--context', - 'kind-' + kube_context, - '-n', - 'istio-system', - 'get', - 'service', - 'istio-ingressgateway', - '-o', - 'jsonpath={.status.loadBalancer.ingress[0].ip}'], - capture_output=True, check=True) - logger.info('Platform can be accessed through the URL:') - logger.info(u'\u2023' + ' http://' + gw_url.stdout.decode('utf-8')) - kube_info = s.run(['kubectl', 'cluster-info'], capture_output=True, check=True) # noqa - logger.info(kube_info.stdout.decode('utf-8')) - except s.CalledProcessError as error: - logger.error(error.stderr.decode('utf-8')) - raise click.Abort() - - -# Generate keys -@cli.command('keys', short_help='Generate keys') -@click.argument('bits', - required=True, - default=2048) -@click.pass_context -@pass_environment -def keys(ctx, kube_context_cli, bits): - """Generate encryption keys used by the application services""" - # Locations, we can argument these if need be - keys_dir = 'infrastructure/keys/' - private_key_filename = 'key.pem' - public_key_filename = 'key.pub' - - # Check if keys directory exists - if not os.path.exists(keys_dir): - logger.info("Creating key directory...") - os.makedirs(keys_dir) - logger.info("Keys directory already exists") - - # Check if the keys exist and warn user - if ( - os.path.isfile(keys_dir + private_key_filename) or - os.path.isfile(keys_dir + public_key_filename) - ): - if not click.confirm('Keys already exist, overwrite y/n?'): - raise click.Abort() - - # Generate the keys using cryptography - logger.info('Generating keys...') - with click_spinner.spinner(): - key = rsa.generate_private_key( - backend=default_backend(), - public_exponent=65537, - key_size=bits - ) - private_key = key.private_bytes( - serialization.Encoding.PEM, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption()) - public_key = key.public_key().public_bytes( - serialization.Encoding.PEM, - serialization.PublicFormat.SubjectPublicKeyInfo - ) - - # Write the private key - f = open(keys_dir + private_key_filename, 'wb') - f.write(private_key) - f.close() - - # Write the public key - f = open(keys_dir + public_key_filename, 'wb') - f.write(public_key) - f.close() - logger.info('Keys generated successfully.') - - -@cli.command('release', short_help='Make a platform release') -@click.argument('version', type=semver.BasedVersionParamType(), required=True) -@click.argument('submodule_path', required=True, type=click.Path(exists=True)) -@click.pass_context -@pass_environment -def release(ctx, kube_context_cli, version, submodule_path): - """Release platform by tagging platform repo and - tagging the individual component (git submodule) - using its respective SHA that the submodule points at""" - submodule_name = os.path.basename(submodule_path) - - # Get the repository - repo = get_repo(os.getcwd()) - if not repo: - click.echo("Repository not found.") - return - - # Check if submodule exists in the repository - submodule = next((s for s in repo.submodules if s.name.endswith(submodule_name)), None) # noqa - if not submodule: - click.echo(f"Submodule '{submodule_name}' not found in the repository.") # noqa - return - - # Tag platform at provided version - platform_tag_name = f"v{version}" - tag_result = tag_repo(repo, platform_tag_name) - - if tag_result: - click.echo(f"Platform version: {platform_tag_name}") - else: - click.echo("Failed to tag platform") - - submodule_path = os.path.join(repo.working_dir, submodule_path) - submodule_repo = git.Repo(submodule_path) - submodule_sha = submodule_repo.head.commit.hexsha - submodule_tag_name = f"{submodule_name}-{platform_tag_name}" - tag_result = tag_repo(submodule_repo, submodule_tag_name, submodule_sha) - if tag_result: - click.echo(f"{submodule_name} version: {platform_tag_name}") - else: - click.echo(f"Failed to tag {submodule_name} at {submodule_sha}") - - -def tag_repo(repo, tag_name, commit_sha=None): - try: - if commit_sha: - repo.create_tag(tag_name, ref=commit_sha) - else: - repo.create_tag(tag_name) - return True - except git.GitCommandError as e: - if "already exists" in str(e): - return True # Tag already exists - logger.error(f"Error tagging repository: {e}") - return False - - -@cli.command('version', short_help='Get all versions of components') -@click.argument('submodules', - required=True, - default='all') -@click.argument('repopath', - required=True, - type=click.Path(exists=True), - default=os.getcwd()) -@click.pass_context -@pass_environment -def version(ctx, kube_context_cli, submodules, repopath): - """Check versions of microservices in git submodules - You can provide a comma-separated list of submodules - or you can use 'all' for all submodules""" - - # Get the repo from arguments defaults to cwd - try: - repo = get_repo(repopath) - logger.info("REPO") - logger.info(repo) - submodules = get_submodules(repo, submodules) - except Exception as e: # noqa - logger.info("An error occurred while getting submodule") - - version_info = [] - # Retrieve version information for each submodule - for submodule in submodules: - submodule_path = os.path.join(repo.working_dir, submodule) - try: - smrepo = git.Repo(submodule_path) - tags = smrepo.tags - # Choose the latest tag as version - latest_tag = max(tags, key=lambda t: t.commit.committed_datetime) - version_info.append((submodule, latest_tag.name)) - except Exception as e: # noqa - version_info.append((submodule, "Error retrieving version")) - - for submodule, version in version_info: - logger.info(f"{submodule}: {version}") diff --git a/enabler/commands/cmd_setup.py b/enabler/commands/cmd_setup.py deleted file mode 100755 index 5339034..0000000 --- a/enabler/commands/cmd_setup.py +++ /dev/null @@ -1,437 +0,0 @@ -from enabler.cli import pass_environment, logger - -import click -import click_spinner -import subprocess as s -import docker -import ipaddress -import urllib.request -import os -import stat -import tarfile -import yaml -import requests -import semver -import re - - -# Setup group of commands -@click.group('setup', short_help='Setup infrastructure services') -@click.pass_context -@pass_environment -def cli(ctx, kube_context_cli): - """Setup infrastructure services on kubernetes. - The name of the context is taken from the option --kube-context - which defaults to 'keitaro'""" - pass - - -# Fetch all binaries for the dependencies -@cli.command('init', short_help='Initialize dependencies') -@click.pass_context -@pass_environment -def init(ctx, kube_context_cli): - """Download binaries for all dependencies""" - - # Check if bin folder exists - check_bin_folder() - - # Figure out what kind of OS are we on - ostype = os.uname().sysname.lower() - - # Load URLs from the JSON file - enabler_path = get_path() - file_path = os.path.join(enabler_path, 'enabler/dependencies.yaml') - with open(file_path, 'r') as f: - urls = yaml.safe_load(f) - - # Use the URLs - kubectl_url = urls["kubectl"].format(ostype) - helm_url = urls["helm"].format(ostype) - istioctl_url = urls["istioctl"].format(ostype) - kind_url = urls["kind"].format(ostype) - skaffold_url = urls["skaffold"].format(ostype) - - with click_spinner.spinner(): - # Download kubectl if not exists - kubectl_location = 'bin/kubectl' - if os.path.exists(kubectl_location): - logger.info(f'kubectl already exists at: {kubectl_location}') - else: - logger.info('Downloading kubectl...') - download_and_make_executable(kubectl_url, kubectl_location) - - # Download helm if not exists or update if necessary - helm_location = 'bin/helm' - if os.path.exists(helm_location): - logger.info(f'helm already exists at: {helm_location}') - update_binary_if_necessary(helm_location, helm_url, ostype) - else: - logger.info('Downloading helm...') - download_and_make_executable(helm_url, helm_location) - - # Download istioctl if not exists or update if necessary - istioctl_location = 'bin/istioctl' - if os.path.exists(istioctl_location): - logger.info(f'istioctl already exists at: {istioctl_location}') - update_binary_if_necessary(istioctl_location, istioctl_url, ostype) - else: - logger.info('Downloading istioctl...') - download_and_make_executable(istioctl_url, istioctl_location) - - # Download kind if not exists or update if necessary - kind_location = 'bin/kind' - if os.path.exists(kind_location): - logger.info(f'kind already exists at: {kind_location}') - update_binary_if_necessary(kind_location, kind_url, ostype) - else: - logger.info('Downloading kind...') - download_and_make_executable(kind_url, kind_location) - - # Download skaffold if not exists - skaffold_location = 'bin/skaffold' - if os.path.exists(skaffold_location): - logger.info(f'skaffold already exists at: {skaffold_location}') - else: - logger.info('Downloading skaffold...') - download_and_make_executable(skaffold_url, skaffold_location) - - logger.info('All dependencies downloaded to bin/') - logger.info('IMPORTANT: Please add the path to your user profile to ' + - os.getcwd() + '/bin directory at the beginning of your PATH') - logger.info('$ echo export PATH=' + os.getcwd() + '/bin:$PATH >> ~/.profile') # noqa - logger.info('$ source ~/.profile') - - -def get_latest_version_from_github(repo_url, ostype): - try: - # Fetch the GitHub releases page - response = requests.get(repo_url) - response.raise_for_status() - latest_version = response.json()["tag_name"] - if latest_version: - return latest_version - else: - logger.error("Failed to find latest release tag") - return None - except requests.exceptions.RequestException as e: - logger.error(f"Error fetching latest version from GitHub: {e}") - return None - - -def get_current_version(binary_location, binary_name): - if os.path.exists(binary_location): - filename = os.path.basename(binary_location) - version = extract_version_from_filename(filename, binary_name) - return version - else: - logger.error(f"Binary {binary_name} not found at location: {binary_location}") # noqa - return None - - -def extract_version_from_filename(filename, binary_name): - if binary_name in filename: - # Check if the filename contains the binary_name - if binary_name == "helm" or binary_name == "istioctl": - match = re.search(r'{}-v(\d+\.\d+\.\d+)'.format(binary_name), filename) # noqa - if match: - return match.group(1) - elif binary_name == "kind": - match = re.search(r'{}-(\d+\.\d+\.\d+)'.format(binary_name), filename) # noqa - if match: - return match.group(1) - return None - - -def download_and_make_executable(url, destination): - urllib.request.urlretrieve(url, destination) - st = os.stat(destination) - os.chmod(destination, st.st_mode | stat.S_IEXEC) - logger.info(f'{os.path.basename(destination)} downloaded and made executable!') # noqa - - -def update_binary_if_necessary(binary_location, binary_url, ostype): - current_version = get_current_version(binary_location, os.path.basename(binary_url)) # noqa - latest_version = get_latest_version_from_github(binary_url, ostype) - if latest_version is not None: - if semver.compare(current_version, latest_version) < 0: - logger.info(f"Updating {os.path.basename(binary_location)}...") - urllib.request.urlretrieve(binary_url, f'{binary_location}.tar.gz') - tar = tarfile.open(f'{binary_location}.tar.gz', 'r:gz') - for member in tar.getmembers(): - if member.isreg(): - member.name = os.path.basename(member.name) - tar.extract(member, os.path.dirname(binary_location)) - tar.close() - os.remove(f'{binary_location}.tar.gz') - logger.info(f'{os.path.basename(binary_location)} updated!') - - -# Metallb setup -@cli.command('metallb', short_help='Setup metallb') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.option('--ip-addresspool', - help='IP Address range to be assigned to metallb, default is last 10 addresses from kind network.' # noqa - 'The IP address range should be in the kind network in order for the application to work properly.', # noqa - required=False) -@click.option('--version', - help='Version of metallb from bitnami. Default version is 4.6.0', - default='4.6.0') -@click.pass_context -@pass_environment -def metallb(ctx, kube_context_cli, kube_context, ip_addresspool, version): - """Install and setup metallb on k8s""" - # Check if metallb is installed - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - - try: - metallb_exists = s.run(['helm', - 'status', - 'metallb', - '-n', - 'metallb', - '--kube-context', - 'kind-' + kube_context], - capture_output=True, check=True) - logger.info('Metallb is already installed, exiting...') - logger.debug(metallb_exists.stdout.decode('utf-8')) - raise click.Abort() - except s.CalledProcessError: - logger.info('Metallb not found. Installing...') - pass - try: - metallb_repo_add = s.run(['helm', - 'repo', - 'add', - 'bitnami', - 'https://charts.bitnami.com/bitnami'], - capture_output=True, check=True) - logger.info('Downloading metallb version ...') - logger.debug(metallb_repo_add.stdout.decode('utf-8')) - metallb_repo_add = s.run(['helm', - 'repo', - 'update'], - capture_output=True, check=True) - logger.info('Repo update ...') - logger.debug(metallb_repo_add.stdout.decode('utf-8')) - - except s.CalledProcessError as error: - logger.info('Metallb repo not found.') - logger.error(error.stdout.decode('utf-8')) - raise click.Abort() - - # Get the Subnet of the kind network - client = docker.from_env() - networks = client.networks() - - # Find the network with name 'kind' - kind_network = None # Initialize kind_network outside the loop - for network in networks: - if network['Name'] == 'kind': - kind_network = network - break - - if kind_network is not None: - # Do something with kind_network - logger.info("Kind network found:", kind_network) - kind_subnet = kind_network['IPAM']['Config'][0]['Subnet'] - else: - logger.info("Kind network not found.") - - # Extract the last 10 ip addresses of the kind subnet - ips = [str(ip) for ip in ipaddress.IPv4Network(kind_subnet)] - metallb_ips = ips[-10:] - - if ip_addresspool is None: - ip_addresspool = metallb_ips[0] + ' - ' + metallb_ips[-1] - else: - # Check if ip address range is in kind network and print out a warning - ip_range = ip_addresspool.strip().split('-') - try: - start_ip = ipaddress.IPv4Address(ip_range[0]) - end_ip = ipaddress.IPv4Address(ip_range[1]) - except Exception: - logger.error('Incorrect IP address range: ' + ip_addresspool) - - if start_ip not in ipaddress.IPv4Network(kind_subnet) or end_ip not in ipaddress.IPv4Network(kind_subnet): # noqa - logger.error('Provided IP address range not in kind network.') - logger.error('Kind subnet is: ' + kind_subnet) - raise click.Abort() - - # Check if version is 3.x.x and then use config map to install, else if version 4.x.x use CDR file for installing. # noqa - # And dynamically set the IP Address range in the compatible .yaml file - if version.split('.')[0] == '3': - yaml_file_path = 'enabler/metallb-configmap.yaml' - with open(yaml_file_path, 'r') as yaml_file: - config = yaml.safe_load(yaml_file) - modified_string = config['data']['config'][:-32]+ip_addresspool+"\n" - config['data']['config'] = modified_string - - logger.info('Metallb will be configured in Layer 2 mode with the range: ' + ip_addresspool) # noqa - updated_yaml = yaml.dump(config, default_flow_style=False) - with open(yaml_file_path, 'w') as yaml_file: - yaml_file.write(updated_yaml) - - elif int(version.split('.')[0]) >= 4: - yaml_file_path = 'metallb-crd.yaml' - with open(yaml_file_path, 'r') as yaml_file: - config = list(yaml.safe_load_all(yaml_file)) - - - logger.info('Metallb will be configured in Layer 2 mode with the range: ' + ip_addresspool) # noqa - for doc in config: - if 'kind' in doc and doc['kind'] == 'IPAddressPool': - doc['spec']['addresses'] = [ip_addresspool] - - with open(yaml_file_path, 'w') as yaml_file: - yaml.dump_all(config, yaml_file, default_flow_style=False) - else: - logger.info('Incompatible format for Metallb version. Please check official versions ') # noqa - ns_exists = s.run(['kubectl', - 'get', - 'ns', - 'metallb', - '--context', - 'kind-' + kube_context], - capture_output=True) - if ns_exists.returncode != 0: - try: - metallb_ns = s.run(['kubectl', # noqa - 'create', - 'ns', - 'metallb', - '--context', - 'kind-' + kube_context], - capture_output=True, check=True) - logger.info('Created a namespace for metallb') - except s.CalledProcessError as error: - logger.error('Could not create namespace for metallb: ' + - error.stderr.decode('utf-8')) - raise click.Abort() - else: - logger.info('Skipping creation of metallb namespace ' - 'since it already exists.') - # Install metallb on the cluster - try: - helm_metallb = s.run(['helm', - 'install', - 'metallb', - '--kube-context', - 'kind-' + kube_context, - '--version', - version, - 'bitnami/metallb', - '-n', - 'metallb', - '--wait'], - capture_output=True, check=True) - - # Apply configuration from CRD file - config_metallb = s.run(['kubectl', # noqa - 'apply', - '-f', - yaml_file_path], capture_output=True, check=True) # noqa - - logger.info('✓ Metallb installed on cluster.') - logger.debug(helm_metallb.stdout.decode("utf-8")) - except s.CalledProcessError as error: - logger.error('Could not install metallb') - logger.error(error.stderr.decode('utf-8')) - - -# Istio setup -@cli.command('istio', short_help='Setup Istio') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.argument('monitoring_tools', - required=False - ) -@click.pass_context -@pass_environment -def istio(ctx, kube_context_cli, kube_context, monitoring_tools): - """Install and setup istio on k8s""" - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - - # Run verify install to check whether we are ready to install istio - try: - istio_verify = s.run(['istioctl', - 'verify-install', - '--context', - 'kind-' + kube_context], - capture_output=True, check=True) - logger.info('Istio pre-check passed. Proceeding with install') - logger.info(istio_verify.stderr.decode('utf-8')) - except s.CalledProcessError as error: - logger.critical('Istio pre-check failed') - logger.critical(error.stderr.decode('utf-8')) - raise click.Abort() - - # Install Istio - logger.info('Installing istio, please wait...') - with click_spinner.spinner(): - istio_command = ['istioctl', - 'manifest', - 'apply', - '-y', - '--set', - 'profile=default'] - if monitoring_tools == 'monitoring-tools': - monitoring_config = ['--set', - 'addonComponents.grafana.enabled=true', - '--set', - 'addonComponents.kiali.enabled=true', - '--set', - 'addonComponents.prometheus.enabled=true', - '--set', - 'addonComponents.tracing.enabled=true', - '--set', - 'values.kiali.dashboard.jaegerURL=http://jaeger-query:16686', # noqa - '--set', - 'values.kiali.dashboard.grafanaURL=http://grafana:3000'] # noqa - - istio_command.extend(monitoring_config) - istio_command.append('--context') - istio_command.append('kind-' + kube_context) - istio_command.append('--wait') - try: - istio_install = s.run(istio_command, - capture_output=True, check=True) - logger.info('Istio installed') - logger.debug(istio_install.stdout.decode('utf-8')) - except s.CalledProcessError as error: - logger.critical('Istio installation failed') - logger.critical(error.stderr.decode('utf-8')) - raise click.Abort() - if monitoring_tools == 'monitoring-tools': - try: - grafana_virtual_service = s.run(['kubectl', 'apply', '-f', 'enabler/grafana-vs.yaml'], capture_output=True, check=True) # noqa - except Exception as e: - logger.error('Error setting grafana URL') - logger.error(str(e)) - - -def get_path(): - enabler_path = os.getcwd() - return enabler_path - - -def check_bin_folder(): - enabler_path = os.getcwd() - full_path = enabler_path + '/bin' - if not os.path.exists(full_path): - logger.info("Creating bin folder...") - os.makedirs(full_path) - else: - logger.info("Bin folder already exists. Continue...") - pass diff --git a/enabler/helpers/git.py b/enabler/helpers/git.py deleted file mode 100644 index 660feee..0000000 --- a/enabler/helpers/git.py +++ /dev/null @@ -1,28 +0,0 @@ -from enabler.cli import logger -import click -import git -import os - - -def get_repo(repopath): - """Function to get the repository.""" - if not os.path.exists(repopath): - logger.critical('The repo path ' + repopath + ' does not exist') - raise click.Abort() - - try: - return git.Repo(repopath, odbt=git.GitDB, search_parent_directories=True) # noqa - except git.InvalidGitRepositoryError: - logger.critical('The repo path ' + repopath + ' is not a git repo') - raise click.Abort() - - -def get_submodules(repo, submodules): - """Function to get submodules.""" - if submodules == 'all': - submodules = [submodule.name for submodule in repo.submodules] - else: - submodules = submodules.split(',') - logger.debug('The provided submodules are:') - logger.debug(submodules) - return submodules diff --git a/enabler/helpers/kube.py b/enabler/helpers/kube.py deleted file mode 100644 index 6b6ec25..0000000 --- a/enabler/helpers/kube.py +++ /dev/null @@ -1,37 +0,0 @@ -from enabler.cli import logger -import subprocess as s - - -def kubectl_info(cluster): - # Get kubectl cluster-info - try: - logger.debug('Running: `kubectl cluster-info`') - result = s.run(['kubectl', - 'cluster-info', - '--context', - 'kind-' + cluster], - capture_output=True, check=True) - logger.debug(result.stdout.decode('utf-8')) - return True - except s.CalledProcessError as error: - logger.debug(error.stderr.decode('utf-8')) - return False - - -def kubeconfig_set(cont, cluster): - # Get mapped control plane port from docker - port = cont.attrs['NetworkSettings']['Ports']['6443/tcp'][0]['HostPort'] - try: - logger.debug('Running: `kubectl config set-cluster`') - result = s.run(['kubectl', - 'config', - 'set-cluster', - 'kind-' + cluster, - '--server', - 'https://127.0.0.1:' + port], - capture_output=True, check=True) - logger.debug(result.stdout.decode('utf-8')) - return True - except s.CalledProcessError as error: - logger.debug(error.stderr.decode('utf-8')) - return False diff --git a/setup.py b/setup.py index b556246..9f6185b 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="enabler", version="0.1", - packages=["enabler", "enabler.commands", "enabler.helpers"], + packages=["enabler", "src.enabler_keitaro_inc.commands", "src.enabler_keitaro_inc.helpers"], include_package_data=True, install_requires=["click==7.1.1", "click-log==0.3.2", @@ -16,6 +16,6 @@ "flake8>=7.0.0"], entry_points=""" [console_scripts] - enabler=enabler.cli:cli + enabler=src.enabler_keitaro_inc.enabler:cli """, ) diff --git a/src/enabler/__init__.py b/src/enabler/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/enabler/commands/__init__.py b/src/enabler/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/enabler/commands/cmd_apps.py b/src/enabler/commands/cmd_apps.py deleted file mode 100644 index 6c7768b..0000000 --- a/src/enabler/commands/cmd_apps.py +++ /dev/null @@ -1,70 +0,0 @@ -from src.enabler.enabler import pass_environment, logger - -import click -import subprocess as s - - -# App group of commands -@click.group('apps', short_help='App commands') -@click.pass_context -@pass_environment -def cli(ctx, kube_context_cli): - """Application specific commands such as creation of kubernetes - objects such as namespaces, configmaps etc. The name of the - context is taken from the option --kube-context - which defaults to 'keitaro'""" - pass - - -# Namespace setup -@cli.command('namespace', short_help='Create namespace') -@click.option('--kube-context', - help='The kubernetes context to use', - required=False) -@click.argument('name', - required=True) -@click.pass_context -@pass_environment -def ns(ctx, kube_context_cli, kube_context, name): - """Create a namespace with auto-injection""" - - if ctx.kube_context is not None: - kube_context = ctx.kube_context - if ctx.kube_context is None and kube_context is None: - logger.error("--kube-context was not specified") - raise click.Abort() - - # Create a namespace in kubernetes - ns_exists = s.run(['kubectl', - 'get', - 'ns', - name, - '--context', - 'kind-' + kube_context], - capture_output=True) - if ns_exists.returncode != 0: - try: - app_ns = s.run(['kubectl', # noqa - 'create', - 'ns', - name, - '--context', - 'kind-' + kube_context], - capture_output=True, check=True) - logger.info('Created a namespace for ' + name) - app_ns_label = s.run(['kubectl', # noqa - 'label', - 'namespace', - name, - 'istio-injection=enabled', - '--context', - 'kind-' + kube_context], - capture_output=True, check=True) - logger.info('Labeled ' + name + ' namespace for istio injection') - except s.CalledProcessError as error: - logger.error('Something went wrong with namespace: ' + - error.stderr.decode('utf-8')) - raise click.Abort() - else: - logger.info('Skipping creation of ' + name + ' namespace ' - 'since it already exists.') diff --git a/src/enabler/commands/cmd_preflight.py b/src/enabler/commands/cmd_preflight.py deleted file mode 100644 index a5a7a69..0000000 --- a/src/enabler/commands/cmd_preflight.py +++ /dev/null @@ -1,112 +0,0 @@ -from src.enabler.enabler import pass_environment, logger - -import click -import subprocess as s - - -@click.command('preflight', short_help='Preflight checks') -@click.pass_context -@pass_environment -def cli(ctx, kube_context_cli): - """Preflight checks to ensure all tools and versions are present""" - # Check java - try: - java_ver = s.run(['java', '-version'], - capture_output=True, check=True) - # Check that we have java 11 - java_major_ver = java_ver.stderr.decode('utf-8').split()[2].strip('"') - if java_major_ver[:2] != '11': - logger.error( - 'Java JDK 11 needed, please change the version of java on your system') # noqa - else: - logger.info('✓ java jdk 11') - logger.debug(java_ver.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('java not found in PATH.') - except s.CalledProcessError as error: - logger.critical('java -version returned something unexpected: ' + - error.stderr.decode('utf-8')) - - # Check docker - try: - docker_ps = s.run(['docker', 'ps'], - capture_output=True, check=True) - logger.info('✓ docker') - logger.debug(docker_ps.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('docker not found in PATH.') - except s.CalledProcessError as error: - logger.critical('`docker ps` returned something unexpected: ' + - error.stderr.decode('utf-8')) - logger.critical('Please ensure the docker daemon is running and that ' - 'your user is part of the docker group. See README') - - # Check helm 3 - try: - helm_ver = s.run(['helm', 'version', '--short'], - capture_output=True, check=True) - # Check that we have helm 3 - if helm_ver.stdout.decode('utf-8')[1] != "3": - logger.error( - 'Old version of helm detected when running "helm" from PATH.') - else: - logger.info('✓ helm 3') - logger.debug(helm_ver.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('helm not found in PATH.') - except s.CalledProcessError as error: - logger.critical('helm version returned something unexpected: ' + - error.stderr.decode('utf-8')) - - # Check kind - try: - kind_ver = s.run(['kind', 'version'], - capture_output=True, check=True) - logger.info('✓ kind') - logger.debug(kind_ver.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('kind not found in PATH.') - except s.CalledProcessError as error: - logger.critical('kind version returned something unexpected: ' + - error.stderr.decode('utf-8')) - - # Check skaffold - try: - skaffold_ver = s.run(['skaffold', 'version'], - capture_output=True, check=True) - logger.info('✓ skaffold') - logger.debug(skaffold_ver.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('skaffold not found in PATH.') - except s.CalledProcessError as error: - logger.critical('skaffold version returned something unexpected: ' + - error.stderr.decode('utf-8')) - - # Check kubectl - try: - kubectl_ver = s.run(['kubectl', 'version', '--client=true'], - capture_output=True, check=True) - logger.info('✓ kubectl') - logger.debug(kubectl_ver.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('kubectl not found in PATH.') - except s.CalledProcessError as error: - logger.critical('kubectl version returned something unexpected: ' + - error.stderr.decode('utf-8')) - - # Check istioctl - try: - istioctl_ver = s.run(['istioctl', 'version', '-s', '--remote=false'], - capture_output=True, check=True) - # Check that we have istio 1.5 or higher - if istioctl_ver.stdout.decode('utf-8')[2] < "5": - logger.error( - 'Old version of istio detected when running "istioctl" from PATH.') # noqa - else: - logger.info('✓ istioctl') - logger.debug(istioctl_ver.stdout.decode('utf-8')) - except FileNotFoundError: - logger.critical('istioctl not found in PATH.') - except s.CalledProcessError as error: - logger.critical('istioctl version returned something unexpected: ' + - error.stderr.decode('utf-8')) diff --git a/src/enabler/commands/cmd_version.py b/src/enabler/commands/cmd_version.py deleted file mode 100644 index fb42963..0000000 --- a/src/enabler/commands/cmd_version.py +++ /dev/null @@ -1,13 +0,0 @@ -from src.enabler.enabler import pass_environment, logger -import pkg_resources -import click - -# Command to get the current Enabler version -@click.group('version', short_help='Get current version of Enabler', invoke_without_command=True) # noqa -@click.pass_context -@pass_environment -def enabler_version(ctx, kube_context_cli): - """Get current version of Enabler""" - distribution = pkg_resources.get_distribution("enabler") - version = distribution.version - logger.info("Enabler "+version) diff --git a/src/enabler/commands/enabler_completion.sh b/src/enabler/commands/enabler_completion.sh deleted file mode 100755 index 80853f8..0000000 --- a/src/enabler/commands/enabler_completion.sh +++ /dev/null @@ -1,30 +0,0 @@ -_enabler_complete() { - local cur_word prev_word - - # Get the current and previous words - cur_word="${COMP_WORDS[COMP_CWORD]}" - prev_word="${COMP_WORDS[COMP_CWORD-1]}" - - case "$prev_word" in - "enabler") # noqa - COMPREPLY=( $(compgen -W "apps kind preflight platform setup version" -- "$cur_word") ) - ;; - "apps") - COMPREPLY=( $(compgen -W "namespace" -- "$cur_word") ) - ;; - "platform") - COMPREPLY=( $(compgen -W "init info keys release version" -- "$cur_word") ) - ;; - "kind") - COMPREPLY=( $(compgen -W "create delete status start stop" -- "$cur_word") ) - ;; - "setup") - COMPREPLY=( $(compgen -W "init metallb istio" -- "$cur_word") ) - ;; - *) - COMPREPLY=() - ;; - esac -} - -complete -F _enabler_complete enabler \ No newline at end of file diff --git a/src/enabler/dependencies.yaml b/src/enabler/dependencies.yaml deleted file mode 100644 index 8cb31aa..0000000 --- a/src/enabler/dependencies.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kubectl: "https://storage.googleapis.com/kubernetes-release/release/latest/bin/{}/amd64/kubectl" # noqa -helm: "https://get.helm.sh/helm-v3.1.2-{}-amd64.tar.gz" # noqa -istioctl: "https://github.com/istio/istio/releases/download/1.5.1/istioctl-1.5.1-{}.tar.gz" # noqa -kind: "https://github.com/kubernetes-sigs/kind/releases/download/v0.22.0/kind-{}-amd64" # noqa -skaffold: "https://storage.googleapis.com/skaffold/releases/latest/skaffold-{}-amd64" # noqa \ No newline at end of file diff --git a/src/enabler/grafana-vs.yaml b/src/enabler/grafana-vs.yaml deleted file mode 100644 index 0c40755..0000000 --- a/src/enabler/grafana-vs.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: networking.istio.io/v1beta1 -kind: Gateway -metadata: - name: grafana-gateway - namespace: istio-system -spec: - selector: - app: istio-ingressgateway - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - tls: - httpsRedirect: false - hosts: - - "grafana.local" - ---- -apiVersion: networking.istio.io/v1alpha3 -kind: VirtualService -metadata: - name: grafana-vs - namespace: istio-system -spec: - hosts: - - "grafana.local" - gateways: - - grafana-gateway - http: - - route: - - destination: - port: - number: 3000 - host: grafana diff --git a/src/enabler/helpers/__init__.py b/src/enabler/helpers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/enabler/helpers/kind.py b/src/enabler/helpers/kind.py deleted file mode 100644 index 99c406c..0000000 --- a/src/enabler/helpers/kind.py +++ /dev/null @@ -1,20 +0,0 @@ -from src.enabler.enabler import logger - -import click -import subprocess as s - - -def kind_get(cluster): - # Get kind clusters - try: - logger.debug('Running: `kind get clusters`') - result = s.run(['kind', 'get', 'clusters'], - capture_output=True, check=True) - kind_clusters = result.stdout.decode('utf-8').splitlines() - if cluster in kind_clusters: - return True - else: - return False - except s.CalledProcessError as error: - logger.critical(error.stderr.decode('utf-8')) - raise click.Abort() diff --git a/src/enabler/type/__init__.py b/src/enabler/type/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/enabler/type/semver.py b/src/enabler/type/semver.py deleted file mode 100644 index 617edd3..0000000 --- a/src/enabler/type/semver.py +++ /dev/null @@ -1,20 +0,0 @@ -from semver import parse -import click - - -class BasedVersionParamType(click.ParamType): - name = "semver" - - def convert(self, value, param, ctx): - try: - parse(value) - return (value) - except TypeError: - self.fail( - '{value!r} is not a valid version, please use semver', - param, - ctx, - ) - except ValueError: - self.fail(f'{value!r} is not a valid version, please use semver', - param, ctx) diff --git a/enabler/__init__.py b/src/enabler_keitaro_inc/__init__.py similarity index 100% rename from enabler/__init__.py rename to src/enabler_keitaro_inc/__init__.py diff --git a/enabler/commands/__init__.py b/src/enabler_keitaro_inc/commands/__init__.py similarity index 100% rename from enabler/commands/__init__.py rename to src/enabler_keitaro_inc/commands/__init__.py diff --git a/enabler/commands/cmd_apps.py b/src/enabler_keitaro_inc/commands/cmd_apps.py similarity index 97% rename from enabler/commands/cmd_apps.py rename to src/enabler_keitaro_inc/commands/cmd_apps.py index 6f455b5..42395b1 100644 --- a/enabler/commands/cmd_apps.py +++ b/src/enabler_keitaro_inc/commands/cmd_apps.py @@ -1,4 +1,4 @@ -from enabler.cli import pass_environment, logger +from src.enabler_keitaro_inc.enabler import pass_environment, logger import click import subprocess as s diff --git a/src/enabler/commands/cmd_kind.py b/src/enabler_keitaro_inc/commands/cmd_kind.py similarity index 98% rename from src/enabler/commands/cmd_kind.py rename to src/enabler_keitaro_inc/commands/cmd_kind.py index 5335087..bb67e4e 100644 --- a/src/enabler/commands/cmd_kind.py +++ b/src/enabler_keitaro_inc/commands/cmd_kind.py @@ -1,5 +1,5 @@ -from src.enabler.enabler import pass_environment, logger -from enabler.helpers import kind, kube +from src.enabler_keitaro_inc.enabler import pass_environment, logger +from src.enabler_keitaro_inc.helpers import kind, kube import click import click_spinner diff --git a/src/enabler/commands/cmd_platform.py b/src/enabler_keitaro_inc/commands/cmd_platform.py similarity index 97% rename from src/enabler/commands/cmd_platform.py rename to src/enabler_keitaro_inc/commands/cmd_platform.py index 632d59b..e770281 100644 --- a/src/enabler/commands/cmd_platform.py +++ b/src/enabler_keitaro_inc/commands/cmd_platform.py @@ -1,6 +1,6 @@ -from src.enabler.enabler import pass_environment, logger -from enabler.helpers.git import get_submodules, get_repo -from enabler.type import semver +from src.enabler_keitaro_inc.enabler import pass_environment, logger +from src.enabler_keitaro_inc.helpers.git import get_submodules, get_repo +from src.enabler_keitaro_inc.type import semver from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa diff --git a/enabler/commands/cmd_preflight.py b/src/enabler_keitaro_inc/commands/cmd_preflight.py similarity index 98% rename from enabler/commands/cmd_preflight.py rename to src/enabler_keitaro_inc/commands/cmd_preflight.py index 2b6aec2..f6beb5e 100644 --- a/enabler/commands/cmd_preflight.py +++ b/src/enabler_keitaro_inc/commands/cmd_preflight.py @@ -1,4 +1,4 @@ -from enabler.cli import pass_environment, logger +from src.enabler_keitaro_inc.enabler import pass_environment, logger import click import subprocess as s diff --git a/src/enabler/commands/cmd_setup.py b/src/enabler_keitaro_inc/commands/cmd_setup.py similarity index 99% rename from src/enabler/commands/cmd_setup.py rename to src/enabler_keitaro_inc/commands/cmd_setup.py index 73a5428..b29c4de 100755 --- a/src/enabler/commands/cmd_setup.py +++ b/src/enabler_keitaro_inc/commands/cmd_setup.py @@ -1,4 +1,4 @@ -from src.enabler.enabler import pass_environment, logger +from src.enabler_keitaro_inc.enabler import pass_environment, logger import click import click_spinner @@ -117,7 +117,7 @@ def get_latest_version_from_github(repo_url, ostype): logger.error("Failed to find latest release tag") return None except requests.exceptions.RequestException as e: - logger.error(f"Error fetching latest version from GitHub: {e}") + logger.info(f"Latest release not found") return None diff --git a/enabler/commands/cmd_version.py b/src/enabler_keitaro_inc/commands/cmd_version.py similarity index 86% rename from enabler/commands/cmd_version.py rename to src/enabler_keitaro_inc/commands/cmd_version.py index a856330..7f5c176 100644 --- a/enabler/commands/cmd_version.py +++ b/src/enabler_keitaro_inc/commands/cmd_version.py @@ -1,4 +1,4 @@ -from enabler.cli import pass_environment, logger +from src.enabler_keitaro_inc.enabler import pass_environment, logger import pkg_resources import click diff --git a/enabler/commands/enabler_completion.sh b/src/enabler_keitaro_inc/commands/enabler_completion.sh similarity index 100% rename from enabler/commands/enabler_completion.sh rename to src/enabler_keitaro_inc/commands/enabler_completion.sh diff --git a/enabler/dependencies.yaml b/src/enabler_keitaro_inc/dependencies.yaml similarity index 84% rename from enabler/dependencies.yaml rename to src/enabler_keitaro_inc/dependencies.yaml index 8cb31aa..4a742ad 100644 --- a/enabler/dependencies.yaml +++ b/src/enabler_keitaro_inc/dependencies.yaml @@ -1,5 +1,5 @@ -kubectl: "https://storage.googleapis.com/kubernetes-release/release/latest/bin/{}/amd64/kubectl" # noqa +kubectl: "https://storage.googleapis.com/kubernetes-release/release/v1.29.0/bin/{}/amd64/kubectl" # noqa helm: "https://get.helm.sh/helm-v3.1.2-{}-amd64.tar.gz" # noqa istioctl: "https://github.com/istio/istio/releases/download/1.5.1/istioctl-1.5.1-{}.tar.gz" # noqa kind: "https://github.com/kubernetes-sigs/kind/releases/download/v0.22.0/kind-{}-amd64" # noqa -skaffold: "https://storage.googleapis.com/skaffold/releases/latest/skaffold-{}-amd64" # noqa \ No newline at end of file +skaffold: "https://storage.googleapis.com/skaffold/releases/latest/skaffold-{}-amd64" # noqa \ No newline at end of file diff --git a/src/enabler/enabler.py b/src/enabler_keitaro_inc/enabler.py similarity index 95% rename from src/enabler/enabler.py rename to src/enabler_keitaro_inc/enabler.py index d072280..4821563 100644 --- a/src/enabler/enabler.py +++ b/src/enabler_keitaro_inc/enabler.py @@ -47,7 +47,7 @@ def list_commands(self, ctx): def get_command(self, ctx, name): try: mod = __import__( - "src.enabler.commands.cmd_{}".format(name), None, None, ["cli"] + "src.enabler_keitaro_inc.commands.cmd_{}".format(name), None, None, ["cli"] ) except ImportError: return diff --git a/enabler/grafana-vs.yaml b/src/enabler_keitaro_inc/grafana-vs.yaml similarity index 100% rename from enabler/grafana-vs.yaml rename to src/enabler_keitaro_inc/grafana-vs.yaml diff --git a/enabler/helpers/__init__.py b/src/enabler_keitaro_inc/helpers/__init__.py similarity index 100% rename from enabler/helpers/__init__.py rename to src/enabler_keitaro_inc/helpers/__init__.py diff --git a/src/enabler/helpers/git.py b/src/enabler_keitaro_inc/helpers/git.py similarity index 94% rename from src/enabler/helpers/git.py rename to src/enabler_keitaro_inc/helpers/git.py index f1a5f6f..b1e0b80 100644 --- a/src/enabler/helpers/git.py +++ b/src/enabler_keitaro_inc/helpers/git.py @@ -1,4 +1,4 @@ -from enabler.enabler import logger +from src.enabler_keitaro_inc.enabler import logger import click import git import os diff --git a/enabler/helpers/kind.py b/src/enabler_keitaro_inc/helpers/kind.py similarity index 91% rename from enabler/helpers/kind.py rename to src/enabler_keitaro_inc/helpers/kind.py index bde87bf..9662c82 100644 --- a/enabler/helpers/kind.py +++ b/src/enabler_keitaro_inc/helpers/kind.py @@ -1,4 +1,4 @@ -from enabler.cli import logger +from src.enabler_keitaro_inc.enabler import logger import click import subprocess as s diff --git a/src/enabler/helpers/kube.py b/src/enabler_keitaro_inc/helpers/kube.py similarity index 96% rename from src/enabler/helpers/kube.py rename to src/enabler_keitaro_inc/helpers/kube.py index 933a04f..bb106f9 100644 --- a/src/enabler/helpers/kube.py +++ b/src/enabler_keitaro_inc/helpers/kube.py @@ -1,4 +1,4 @@ -from enabler.enabler import logger +from src.enabler_keitaro_inc.enabler import logger import subprocess as s diff --git a/enabler/type/__init__.py b/src/enabler_keitaro_inc/type/__init__.py similarity index 100% rename from enabler/type/__init__.py rename to src/enabler_keitaro_inc/type/__init__.py diff --git a/enabler/type/semver.py b/src/enabler_keitaro_inc/type/semver.py similarity index 100% rename from enabler/type/semver.py rename to src/enabler_keitaro_inc/type/semver.py diff --git a/enabler/unit_tests/apps_unittests.py b/tests/apps_unittests.py similarity index 80% rename from enabler/unit_tests/apps_unittests.py rename to tests/apps_unittests.py index bbc9829..2aef24d 100644 --- a/enabler/unit_tests/apps_unittests.py +++ b/tests/apps_unittests.py @@ -1,14 +1,14 @@ import unittest from click.testing import CliRunner from unittest.mock import patch -from enabler.commands.cmd_apps import cli as CLI +from src.enabler_keitaro_inc.enabler import cli as CLI class TestAppCommands(unittest.TestCase): def setUp(self): self.runner = CliRunner() - @patch('enabler.commands.cmd_apps.s') + @patch('src.enabler_keitaro_inc.commands.cmd_apps.s') def test_create_namespace_command(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['namespace', 'test-namespace']) diff --git a/enabler/unit_tests/kind_unittests.py b/tests/kind_unittests.py similarity index 73% rename from enabler/unit_tests/kind_unittests.py rename to tests/kind_unittests.py index 42104db..02c850a 100644 --- a/enabler/unit_tests/kind_unittests.py +++ b/tests/kind_unittests.py @@ -1,34 +1,34 @@ import unittest from click.testing import CliRunner from unittest.mock import MagicMock, patch -from enabler.commands.cmd_kind import cli as CLI +from src.enabler_keitaro_inc.commands.cmd_kind import cli as CLI class TestKindCommands(unittest.TestCase): def setUp(self): self.runner = CliRunner() - @patch('enabler.commands.cmd_kind.s') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.s') def test_create_command(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['create']) self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_kind.s') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.s') def test_delete_command(self, mock_s): mock_s.run.return_value.returncode = 1 result = self.runner.invoke(CLI, ['delete']) self.assertEqual(result.exit_code, 1) - @patch('enabler.commands.cmd_kind.s') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.s') def test_status_command(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['status']) self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_kind.docker') - @patch('enabler.commands.cmd_kind.kube') - @patch('enabler.commands.cmd_kind.click_spinner.spinner') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.docker') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.kube') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.click_spinner.spinner') def test_start_command(self, mock_spinner, mock_kube, mock_docker): mock_kube.kubectl_info.return_value = True mock_container = MagicMock() @@ -38,8 +38,8 @@ def test_start_command(self, mock_spinner, mock_kube, mock_docker): result = self.runner.invoke(CLI, ['start']) self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_kind.docker') - @patch('enabler.commands.cmd_kind.click_spinner.spinner') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.docker') + @patch('src.enabler_keitaro_inc.commands.cmd_kind.click_spinner.spinner') def test_stop_command(self, mock_spinner, mock_docker): mock_container = MagicMock() mock_container.name = 'test-control-plane' diff --git a/enabler/unit_tests/platform_unittests.py b/tests/platform_unittests.py similarity index 78% rename from enabler/unit_tests/platform_unittests.py rename to tests/platform_unittests.py index 0c3638e..3f49446 100644 --- a/enabler/unit_tests/platform_unittests.py +++ b/tests/platform_unittests.py @@ -4,7 +4,7 @@ from click.testing import CliRunner from unittest.mock import patch from git import Repo -from enabler.cli import cli as CLI +from src.enabler_keitaro_inc.enabler import cli as CLI import os @@ -22,27 +22,27 @@ def tearDown(self): # Clean up the temporary directory after the test shutil.rmtree(self.temp_dir) - @patch('enabler.commands.cmd_platform.s') + @patch('src.enabler_keitaro_inc.commands.cmd_platform.s') def test_platform_init(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['platform', 'init', 'all', self.temp_dir]) # noqa self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_platform.s') + @patch('src.enabler_keitaro_inc.commands.cmd_platform.s') def test_platform_info(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['platform', 'info', '--kube-context', '']) # noqa self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_platform.click.confirm') - @patch('enabler.commands.cmd_platform.s') + @patch('src.enabler_keitaro_inc.commands.cmd_platform.click.confirm') + @patch('src.enabler_keitaro_inc.commands.cmd_platform.s') def test_platform_keys(self, mock_s, mock_confirm): - mock_s.run.return_value.returncode = 1 + mock_s.run.return_value.returncode = 0 mock_confirm.return_value = False result = self.runner.invoke(CLI, ['platform', 'keys']) - self.assertEqual(result.exit_code, 1) + self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_platform.s') + @patch('src.enabler_keitaro_inc.commands.cmd_platform.s') def test_platform_release(self, mock_s): mock_s.run.return_value.returncode = 0 simulated_path = 'platform/microservice' @@ -52,7 +52,7 @@ def test_platform_release(self, mock_s): result = self.runner.invoke(CLI, ['platform', 'release', '2.1.7', simulated_path]) # noqa self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_platform.s') + @patch('src.enabler_keitaro_inc.commands.cmd_platform.s') def test_platform_version(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['platform', 'version']) diff --git a/enabler/unit_tests/preflight_unittests.py b/tests/preflight_unittests.py similarity index 85% rename from enabler/unit_tests/preflight_unittests.py rename to tests/preflight_unittests.py index bdb1ce7..dc01a1f 100644 --- a/enabler/unit_tests/preflight_unittests.py +++ b/tests/preflight_unittests.py @@ -1,14 +1,14 @@ import unittest from click.testing import CliRunner from unittest.mock import patch -from enabler.commands.cmd_preflight import cli as CLI +from src.enabler_keitaro_inc.commands.cmd_preflight import cli as CLI class TestPreflightCommands(unittest.TestCase): def setUp(self): self.runner = CliRunner() - @patch('enabler.commands.cmd_preflight.s') + @patch('src.enabler_keitaro_inc.commands.cmd_preflight.s') def test_preflight_command(self, mock_s): mock_s.run.return_value.returncode = 0 with self.runner.isolated_filesystem(): diff --git a/enabler/unit_tests/setup_unittests.py b/tests/setup_unittests.py similarity index 72% rename from enabler/unit_tests/setup_unittests.py rename to tests/setup_unittests.py index f2ed130..2edf3c0 100644 --- a/enabler/unit_tests/setup_unittests.py +++ b/tests/setup_unittests.py @@ -1,7 +1,7 @@ import unittest from click.testing import CliRunner from unittest.mock import MagicMock, patch -from enabler.commands.cmd_setup import cli as CLI +from src.enabler_keitaro_inc.commands.cmd_setup import cli as CLI import os @@ -9,9 +9,9 @@ class TestSetupCommands(unittest.TestCase): def setUp(self): self.runner = CliRunner() - @patch('enabler.commands.cmd_setup.urllib.request') - @patch('enabler.commands.cmd_setup.os.stat') - @patch('enabler.commands.cmd_setup.os.chmod') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.urllib.request') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.os.stat') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.os.chmod') def test_init_command(self, mock_chmod, mock_stat, mock_request): permission = 0o755 @@ -31,10 +31,10 @@ def test_init_command(self, mock_chmod, mock_stat, mock_request): print(result.output) self.assertEqual(result.exit_code, 0) - @patch('enabler.commands.cmd_setup.docker.from_env') - @patch('enabler.commands.cmd_setup.docker.networks') - @patch('enabler.commands.cmd_setup.logger') - @patch('enabler.commands.cmd_setup.s') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.docker.from_env') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.docker.networks') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.logger') + @patch('src.enabler_keitaro_inc.commands.cmd_setup.s') def test_metallb_command(self, mock_s, mock_logger, mock_networks, mock_from_env): # noqa mock_network = MagicMock() mock_network['Name'] = 'kind' diff --git a/enabler/unit_tests/version_unittests.py b/tests/version_unittests.py similarity index 79% rename from enabler/unit_tests/version_unittests.py rename to tests/version_unittests.py index 3b7da7b..f717490 100644 --- a/enabler/unit_tests/version_unittests.py +++ b/tests/version_unittests.py @@ -1,13 +1,13 @@ import unittest from unittest.mock import patch -from enabler.cli import CLI +from src.enabler_keitaro_inc.enabler import CLI class TestVersionCommands(unittest.TestCase): def setUp(self): self.cli = CLI(runner=None) - @patch('enabler.cli.subprocess.run') + @patch('src.enabler_keitaro_inc.enabler.subprocess.run') def test_version_command(self, mock_subprocess_run): mock_subprocess_run.return_value.stdout = 'Enabler 0.1' version = self.cli.version_command() From 87e6e79b19cb0c99da752a5bd884b6c22e221efb Mon Sep 17 00:00:00 2001 From: taleksovska Date: Tue, 30 Apr 2024 17:01:38 +0200 Subject: [PATCH 4/7] Fix lint errors --- setup.py | 2 +- src/enabler_keitaro_inc/commands/cmd_setup.py | 4 ++-- src/enabler_keitaro_inc/enabler.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 9f6185b..c9c6466 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="enabler", version="0.1", - packages=["enabler", "src.enabler_keitaro_inc.commands", "src.enabler_keitaro_inc.helpers"], + packages=["enabler", "src.enabler_keitaro_inc.commands", "src.enabler_keitaro_inc.helpers"], # noqa include_package_data=True, install_requires=["click==7.1.1", "click-log==0.3.2", diff --git a/src/enabler_keitaro_inc/commands/cmd_setup.py b/src/enabler_keitaro_inc/commands/cmd_setup.py index b29c4de..92d2199 100755 --- a/src/enabler_keitaro_inc/commands/cmd_setup.py +++ b/src/enabler_keitaro_inc/commands/cmd_setup.py @@ -116,8 +116,8 @@ def get_latest_version_from_github(repo_url, ostype): else: logger.error("Failed to find latest release tag") return None - except requests.exceptions.RequestException as e: - logger.info(f"Latest release not found") + except requests.exceptions.RequestException as e: # noqa + logger.info("Latest release not found") return None diff --git a/src/enabler_keitaro_inc/enabler.py b/src/enabler_keitaro_inc/enabler.py index 4821563..42ea795 100644 --- a/src/enabler_keitaro_inc/enabler.py +++ b/src/enabler_keitaro_inc/enabler.py @@ -47,7 +47,7 @@ def list_commands(self, ctx): def get_command(self, ctx, name): try: mod = __import__( - "src.enabler_keitaro_inc.commands.cmd_{}".format(name), None, None, ["cli"] + "src.enabler_keitaro_inc.commands.cmd_{}".format(name), None, None, ["cli"] # noqa ) except ImportError: return From 4ba402df648d3083bf11c0475ad1ff9ce0c92a80 Mon Sep 17 00:00:00 2001 From: taleksovska Date: Thu, 9 May 2024 16:47:27 +0200 Subject: [PATCH 5/7] Resolve structure, location and import issues --- .../dependencies.yaml => dependencies.yaml | 0 .../grafana-vs.yaml => grafana-vs.yaml | 0 .../commands/cmd_platform.py | 2 -- src/enabler_keitaro_inc/commands/cmd_setup.py | 23 +++++++++++++++---- 4 files changed, 18 insertions(+), 7 deletions(-) rename src/enabler_keitaro_inc/dependencies.yaml => dependencies.yaml (100%) rename src/enabler_keitaro_inc/grafana-vs.yaml => grafana-vs.yaml (100%) diff --git a/src/enabler_keitaro_inc/dependencies.yaml b/dependencies.yaml similarity index 100% rename from src/enabler_keitaro_inc/dependencies.yaml rename to dependencies.yaml diff --git a/src/enabler_keitaro_inc/grafana-vs.yaml b/grafana-vs.yaml similarity index 100% rename from src/enabler_keitaro_inc/grafana-vs.yaml rename to grafana-vs.yaml diff --git a/src/enabler_keitaro_inc/commands/cmd_platform.py b/src/enabler_keitaro_inc/commands/cmd_platform.py index e770281..7e8d545 100644 --- a/src/enabler_keitaro_inc/commands/cmd_platform.py +++ b/src/enabler_keitaro_inc/commands/cmd_platform.py @@ -218,8 +218,6 @@ def version(ctx, kube_context_cli, submodules, repopath): # Get the repo from arguments defaults to cwd try: repo = get_repo(repopath) - logger.info("REPO") - logger.info(repo) submodules = get_submodules(repo, submodules) except Exception as e: # noqa logger.info("An error occurred while getting submodule") diff --git a/src/enabler_keitaro_inc/commands/cmd_setup.py b/src/enabler_keitaro_inc/commands/cmd_setup.py index 92d2199..d3bb948 100755 --- a/src/enabler_keitaro_inc/commands/cmd_setup.py +++ b/src/enabler_keitaro_inc/commands/cmd_setup.py @@ -41,8 +41,6 @@ def init(ctx, kube_context_cli): # Load URLs from the JSON file enabler_path = get_path() - logger.info("Enabler path") - logger.info(enabler_path) file_path = os.path.join(enabler_path, 'dependencies.yaml') with open(file_path, 'r') as f: urls = yaml.safe_load(f) @@ -69,8 +67,17 @@ def init(ctx, kube_context_cli): logger.info(f'helm already exists at: {helm_location}') update_binary_if_necessary(helm_location, helm_url, ostype) else: + # download_and_make_executable(helm_url, helm_location) logger.info('Downloading helm...') - download_and_make_executable(helm_url, helm_location) + urllib.request.urlretrieve(helm_url, 'bin/helm.tar.gz') + tar = tarfile.open('bin/helm.tar.gz', 'r:gz') + for member in tar.getmembers(): + if member.isreg(): + member.name = os.path.basename(member.name) + tar.extract('helm', 'bin') + tar.close() + os.remove('bin/helm.tar.gz') + logger.info('helm downloaded!') # Download istioctl if not exists or update if necessary istioctl_location = 'bin/istioctl' @@ -78,8 +85,14 @@ def init(ctx, kube_context_cli): logger.info(f'istioctl already exists at: {istioctl_location}') update_binary_if_necessary(istioctl_location, istioctl_url, ostype) else: + # download_and_make_executable(istioctl_url, istioctl_location) logger.info('Downloading istioctl...') - download_and_make_executable(istioctl_url, istioctl_location) + urllib.request.urlretrieve(istioctl_url, 'bin/istioctl.tar.gz') + tar = tarfile.open('bin/istioctl.tar.gz', 'r:gz') + tar.extract('istioctl', 'bin') + tar.close() + os.remove('bin/istioctl.tar.gz') + logger.info('istioctl downloaded!') # Download kind if not exists or update if necessary kind_location = 'bin/kind' @@ -417,7 +430,7 @@ def istio(ctx, kube_context_cli, kube_context, monitoring_tools): raise click.Abort() if monitoring_tools == 'monitoring-tools': try: - grafana_virtual_service = s.run(['kubectl', 'apply', '-f', 'enabler/grafana-vs.yaml'], capture_output=True, check=True) # noqa + grafana_virtual_service = s.run(['kubectl', 'apply', '-f', 'grafana-vs.yaml'], capture_output=True, check=True) # noqa except Exception as e: logger.error('Error setting grafana URL') logger.error(str(e)) From fa29b5436366456c87ba39f1ba3aefa41a12dfd3 Mon Sep 17 00:00:00 2001 From: taleksovska Date: Fri, 10 May 2024 10:45:09 +0200 Subject: [PATCH 6/7] Modify download_and_make_executable function --- src/enabler_keitaro_inc/commands/cmd_setup.py | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/enabler_keitaro_inc/commands/cmd_setup.py b/src/enabler_keitaro_inc/commands/cmd_setup.py index d3bb948..8bb403d 100755 --- a/src/enabler_keitaro_inc/commands/cmd_setup.py +++ b/src/enabler_keitaro_inc/commands/cmd_setup.py @@ -59,7 +59,7 @@ def init(ctx, kube_context_cli): logger.info(f'kubectl already exists at: {kubectl_location}') else: logger.info('Downloading kubectl...') - download_and_make_executable(kubectl_url, kubectl_location) + download_and_make_executable(kubectl_url, kubectl_location, 'kubectl') # noqa # Download helm if not exists or update if necessary helm_location = 'bin/helm' @@ -67,17 +67,7 @@ def init(ctx, kube_context_cli): logger.info(f'helm already exists at: {helm_location}') update_binary_if_necessary(helm_location, helm_url, ostype) else: - # download_and_make_executable(helm_url, helm_location) - logger.info('Downloading helm...') - urllib.request.urlretrieve(helm_url, 'bin/helm.tar.gz') - tar = tarfile.open('bin/helm.tar.gz', 'r:gz') - for member in tar.getmembers(): - if member.isreg(): - member.name = os.path.basename(member.name) - tar.extract('helm', 'bin') - tar.close() - os.remove('bin/helm.tar.gz') - logger.info('helm downloaded!') + download_and_make_executable(helm_url, helm_location, 'helm') # Download istioctl if not exists or update if necessary istioctl_location = 'bin/istioctl' @@ -85,14 +75,7 @@ def init(ctx, kube_context_cli): logger.info(f'istioctl already exists at: {istioctl_location}') update_binary_if_necessary(istioctl_location, istioctl_url, ostype) else: - # download_and_make_executable(istioctl_url, istioctl_location) - logger.info('Downloading istioctl...') - urllib.request.urlretrieve(istioctl_url, 'bin/istioctl.tar.gz') - tar = tarfile.open('bin/istioctl.tar.gz', 'r:gz') - tar.extract('istioctl', 'bin') - tar.close() - os.remove('bin/istioctl.tar.gz') - logger.info('istioctl downloaded!') + download_and_make_executable(istioctl_url, istioctl_location, 'istioctl') # noqa # Download kind if not exists or update if necessary kind_location = 'bin/kind' @@ -101,7 +84,7 @@ def init(ctx, kube_context_cli): update_binary_if_necessary(kind_location, kind_url, ostype) else: logger.info('Downloading kind...') - download_and_make_executable(kind_url, kind_location) + download_and_make_executable(kind_url, kind_location, 'kind') # Download skaffold if not exists skaffold_location = 'bin/skaffold' @@ -109,7 +92,7 @@ def init(ctx, kube_context_cli): logger.info(f'skaffold already exists at: {skaffold_location}') else: logger.info('Downloading skaffold...') - download_and_make_executable(skaffold_url, skaffold_location) + download_and_make_executable(skaffold_url, skaffold_location, 'skaffold') # noqa logger.info('All dependencies downloaded to bin/') logger.info('IMPORTANT: Please add the path to your user profile to ' + @@ -158,11 +141,27 @@ def extract_version_from_filename(filename, binary_name): return None -def download_and_make_executable(url, destination): - urllib.request.urlretrieve(url, destination) - st = os.stat(destination) - os.chmod(destination, st.st_mode | stat.S_IEXEC) - logger.info(f'{os.path.basename(destination)} downloaded and made executable!') # noqa +def download_and_make_executable(url, destination, binary_name): + if binary_name in ['skaffold', 'kind', 'kubectl']: + urllib.request.urlretrieve(url, destination) + st = os.stat(destination) + os.chmod(destination, st.st_mode | stat.S_IEXEC) + logger.info(f'{os.path.basename(destination)} downloaded and made executable!') # noqa + elif binary_name in ['helm', 'istioctl']: + download_and_extract_tar(url, destination, binary_name) + + +def download_and_extract_tar(url, destination, binary_name): + tar_filename = f'bin/{binary_name}.tar.gz' + urllib.request.urlretrieve(url, tar_filename) + tar = tarfile.open(tar_filename, 'r:gz') + for member in tar.getmembers(): + if member.isreg(): + member.name = os.path.basename(member.name) + tar.extract(member, 'bin') + tar.close() + os.remove(tar_filename) + logger.info(f'{binary_name} downloaded and made executable!') def update_binary_if_necessary(binary_location, binary_url, ostype): From 9187ba32c13ab91e50803f4cb79a0a044c766f2b Mon Sep 17 00:00:00 2001 From: taleksovska Date: Fri, 10 May 2024 15:22:47 +0200 Subject: [PATCH 7/7] Fix import issue --- tests/apps_unittests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/apps_unittests.py b/tests/apps_unittests.py index 2aef24d..a6eaa7d 100644 --- a/tests/apps_unittests.py +++ b/tests/apps_unittests.py @@ -1,7 +1,7 @@ import unittest from click.testing import CliRunner from unittest.mock import patch -from src.enabler_keitaro_inc.enabler import cli as CLI +from src.enabler_keitaro_inc.commands.cmd_apps import cli as CLI class TestAppCommands(unittest.TestCase): @@ -13,4 +13,3 @@ def test_create_namespace_command(self, mock_s): mock_s.run.return_value.returncode = 0 result = self.runner.invoke(CLI, ['namespace', 'test-namespace']) self.assertEqual(result.exit_code, 0) - # self.assertIn('Namespace created successfully', result.output)