Skip to content

Commit

Permalink
Sharing, pasting, ctx
Browse files Browse the repository at this point in the history
  • Loading branch information
jiri_otoupal authored and jiri_otoupal committed Feb 28, 2024
1 parent c544e89 commit 5b59bf5
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 22 deletions.
8 changes: 7 additions & 1 deletion abst/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
"CLI Command making OCI Bastion and kubernetes usage simple and fast"
)

__version__ = "2.3.15"
__version__ = "2.3.16"
__author__ = "Jiri Otoupal"
__author_email__ = "jiri-otoupal@ips-database.eu"
__license__ = "MIT"
__url__ = "https://github.com/jiri-otoupal/abst"
__pypi_repo__ = "https://pypi.org/project/abst/"

__version_name__ = "Octopus X0"
__change_log__ = """
Added alias to context, now you can use 'ctx'\n
Sharing and Pasting now works for sets too, you can use set_name/context for both
When pasting, it will ask if you want to change IP and for bastion name renaming
"""
19 changes: 15 additions & 4 deletions abst/bastion_support/oci_bastion.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ def run_ssh_tunnel_managed_session(self, bid, host, private_key_path, username,
f'-o ProxyCommand="ssh -i {private_key_path} -W %h:%p -p {port} {bid}@{host} -A" -p {port} '
f'{username}@{ip} -A')
logging.info(f"Running ssh command {ssh_tunnel_arg_str}")
exit_code = self.__run_ssh_tunnel_call(ssh_tunnel_arg_str, shell, already_split=True)
exit_code = self.__run_ssh_tunnel_call(ssh_tunnel_arg_str, shell,
already_split=True)
logging.info(f"SSH command exit code {exit_code}")
return ssh_tunnel_arg_str, exit_code

Expand Down Expand Up @@ -310,7 +311,8 @@ def init_session_details(cls, creds):
rich.print("If you want to add region, run abst config upgrade <ctx-name>")

ssh_pub_path = str(Path(creds.get("ssh-pub-path",
cfg.get("ssh-pub-path", "No Public key supplied"))).expanduser().resolve())
cfg.get("ssh-pub-path",
"No Public key supplied"))).expanduser().resolve())

if ssh_pub_path == "No Public key supplied":
rich.print(
Expand Down Expand Up @@ -393,7 +395,9 @@ def load_json(cls, path=default_creds_path) -> dict:
if not default_conf_path.exists() and path == default_conf_path:
default_conf_path.parent.mkdir(exist_ok=True)
with open(str(path), "w") as f:
json.dump({"last-check": datetime.datetime.timestamp(datetime.datetime.now())}, f, indent=3)
json.dump(
{"last-check": datetime.datetime.timestamp(datetime.datetime.now())},
f, indent=3)

with open(str(path), "r") as f:
creds = json.load(f)
Expand Down Expand Up @@ -426,7 +430,14 @@ def create_default_locations(cls):

@classmethod
def write_creds_json(cls, td: dict, path: Path):
with open(str(path), "w") as f:

if not path.name.endswith(".json"):
# Construct new filename with .json extension
tmp_path = path.with_suffix(".json")
else:
tmp_path = path

with open(str(tmp_path), "w") as f:
json.dump(td, f, indent=4)
return path

Expand Down
50 changes: 44 additions & 6 deletions abst/cli_commands/context/commands.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import json
import logging
from json import JSONDecodeError
from pathlib import Path

import click
import pyperclip
import rich
from InquirerPy import inquirer

from abst.bastion_support.oci_bastion import Bastion
from abst.config import default_contexts_location, share_excluded_keys
from abst.tools import get_context_path
from abst.utils.misc_funcs import get_context_data, setup_calls
from abst.utils.misc_funcs import get_context_data, setup_calls, get_context_set_data


@click.group(help="Contexts commands")
def context():
pass


@click.group(help="Context commands alias")
def ctx():
pass


@context.command("list", help="Will list all contexts in ~/.abst/context/ folder")
@click.option("--debug", is_flag=True, default=False)
def _list(debug=False):
Expand All @@ -37,13 +44,18 @@ def display(name, debug=False):
rich.print_json(data=data)


@context.command(help="Will print context without local paths and put it in clipboard for sharing")
@context.command(
help="Will print context without local paths and put it in clipboard for sharing")
@click.option("--debug", is_flag=True, default=False)
@click.argument("name")
def share(name, debug=False):
def share(name: str, debug=False):
setup_calls(debug)
rich.print("Copied context into clipboard")
data = get_context_data(name)

if "/" in name:
data = get_context_set_data(name)
else:
data = get_context_data(name)
if data is None:
return
for key in share_excluded_keys:
Expand All @@ -64,5 +76,31 @@ def paste(name, debug=False):
path = get_context_path(name)
if data is None:
return
Bastion.write_creds_json(json.loads(data.replace("'", "\"")), path)
rich.print(f"Wrote config into ~/.abst/contexts/{name}.json")

try:
data_loaded = json.loads(data.replace("'", "\""))
except JSONDecodeError:
rich.print("[red]Invalid data, please try copying context again[/red]")
exit(1)

if data_loaded["default-name"] == "!YOUR NAME!":
data_loaded["default-name"] = inquirer.text(
"Please enter your name for bastion session that will be visible in OCI:").execute()

if inquirer.confirm("Would you like to change IP?").execute():
data_loaded["target-ip"] = inquirer.text(
"Please enter your target IP address:").execute()

if path.exists():
if not inquirer.confirm("File exists. Overwrite?").execute():
rich.print("[red]Canceled.[/red]")
return

Bastion.write_creds_json(data_loaded, path)
rich.print(f"Wrote config into '{path}'")


ctx.add_command(_list, "list")
ctx.add_command(display, "display")
ctx.add_command(share, "share")
ctx.add_command(paste, "paste")
17 changes: 14 additions & 3 deletions abst/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
from InquirerPy import inquirer
from requests import ConnectTimeout

from abst.__version__ import __version_name__, __version__
from packaging import version
from abst.__version__ import __version_name__, __version__, __change_log__
from abst.bastion_support.bastion_scheduler import BastionScheduler
from abst.bastion_support.oci_bastion import Bastion
from abst.cli_commands.config_cli.commands import config
from abst.cli_commands.context.commands import context
from abst.cli_commands.context.commands import context, ctx
from abst.cli_commands.cp_cli.commands import cp
from abst.cli_commands.create_cli.commands import create
from abst.cli_commands.helm_cli.commands import helm
Expand All @@ -26,7 +27,16 @@
@click.group()
@click.version_option(f"{__version__} {__version_name__}")
def cli():
pass
Bastion.create_default_locations()
_config = Bastion.load_config()
if _config.get("changelog-version", None) is None:
_config["changelog-version"] = __version__
elif version.parse(_config["changelog-version"]) > version.parse(
__version__) and __change_log__:
_config["changelog-version"] = __version__
rich.print(f"[yellow]Version {__version__} {__version_name__}[/yellow]")
rich.print("[red]Changelog[/red]")
rich.print(__change_log__)


@cli.command(
Expand Down Expand Up @@ -104,6 +114,7 @@ def main():
cli.add_command(parallel)
cli.add_command(config)
cli.add_command(context)
cli.add_command(ctx)
cli.add_command(helm)
cli.add_command(cp)
cli.add_command(ssh_pod)
Expand Down
22 changes: 19 additions & 3 deletions abst/tools.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
from pathlib import Path
from typing import Optional

import rich

from abst.bastion_support.bastion_scheduler import BastionScheduler
from abst.bastion_support.oci_bastion import Bastion
from abst.config import default_creds_path, default_contexts_location
from abst.config import default_creds_path, default_contexts_location, \
default_parallel_sets_location


def get_context_path(context_name):
if context_name is None:
return default_creds_path
elif "/" in context_name:
path = context_name.split("/")
if len(path) != 2:
rich.print(f"[red]Invalid path {path}[/red]")
return None

set_path = default_parallel_sets_location / path[0]
set_path.mkdir(exist_ok=True)

return set_path / path[1]
else:
return default_contexts_location / (context_name + ".json")

Expand All @@ -17,14 +30,17 @@ def display_scheduled(set_dir: Optional[Path] = None):
from rich.table import Table
from rich.console import Console
console = Console()
table = Table(title=f"Bastions of {set_dir.name} set" if set_dir else "Bastions of default stack", highlight=True)
table = Table(
title=f"Bastions of {set_dir.name} set" if set_dir else "Bastions of default stack",
highlight=True)

table.add_column("Name", justify="left", style="cyan", no_wrap=True)
table.add_column("Local Port", style="magenta", no_wrap=True)
table.add_column("Active", justify="right", style="green", no_wrap=True)
table.add_column("Status", justify="right", style="green", no_wrap=True)
if set_dir:
for context_path in filter(lambda p: not str(p.name).startswith("."), set_dir.iterdir()):
for context_path in filter(lambda p: not str(p.name).startswith("."),
set_dir.iterdir()):
conf = Bastion.load_json(context_path)
context_name = context_path.name[:-5]
table.add_row(context_name, conf.get('local-port', 'Not Specified'),
Expand Down
34 changes: 30 additions & 4 deletions abst/utils/misc_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from rich.logging import RichHandler

from abst.bastion_support.oci_bastion import Bastion
from abst.config import default_contexts_location
from abst.config import default_contexts_location, default_parallel_sets_location


def setup_calls(debug):
Expand Down Expand Up @@ -58,6 +58,29 @@ def get_context_data(name) -> Optional[dict]:
return None


def get_context_set_data(name) -> Optional[dict]:
path = name.split("/")
if len(path) != 2:
rich.print(f"[red]Invalid path '{name}'[/red]")

if path[0] in [file.name for file in
Path(default_parallel_sets_location).iterdir()]:
if path[1] in [file.name.replace(".json", "") for file in
Path(default_parallel_sets_location / path[0]).iterdir()]:
rich.print(f"[bold]Context '{name}' config contents:[/bold]\n")
with open(
Path(default_parallel_sets_location) / path[0] / (path[1] + ".json"),
"r") as f:
data = json.load(f)
return data
else:
rich.print("[red]Context does not exists[/red]")
return None
else:
rich.print("[red]Set does not exists[/red]")
return None


def fetch_pods():
rich.print("Fetching pods")
pod_lines = (
Expand All @@ -68,7 +91,8 @@ def fetch_pods():
return pod_lines


def recursive_copy(dir_to_iter: Path, dest_path: str, exclude: str, data: list, pod_name_precise: str,
def recursive_copy(dir_to_iter: Path, dest_path: str, exclude: str, data: list,
pod_name_precise: str,
thread_list: list):
if dir_to_iter.is_dir():
create_folder_kubectl(data, dest_path, pod_name_precise)
Expand All @@ -83,7 +107,8 @@ def recursive_copy(dir_to_iter: Path, dest_path: str, exclude: str, data: list,

create_folder_kubectl(data, final_dest_path, pod_name_precise)
t = Thread(name=f"recursive_copy_{file}", target=recursive_copy,
args=[file, dest_path, exclude, data, pod_name_precise, thread_list])
args=[file, dest_path, exclude, data, pod_name_precise,
thread_list])
t.start()
thread_list.append(t)
else:
Expand All @@ -106,7 +131,8 @@ def create_folder_kubectl(data: list, dest_path: str, pod_name_precise: str):
os.system(kubectl_create_dir_cmd)


def copy_file_alt_kubectl(data: list, dest_path: str, local_path: Path, pod_name_precise: str, tries=4):
def copy_file_alt_kubectl(data: list, dest_path: str, local_path: Path,
pod_name_precise: str, tries=4):
kubectl_alt_copy_cmd = (f"cat \"{local_path}\" |"
f" kubectl exec -i {pod_name_precise} -n {data[0]} -- tee \"{dest_path}\" > /dev/null")
logging.info(f"Executing {kubectl_alt_copy_cmd}")
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ setuptools~=60.2.0
semantic-version~=2.10.0
eventlet~=0.33.2
pyperclip~=1.8.2
bext~=0.0.8
bext~=0.0.8
requests~=2.28.1
packaging~=23.0

0 comments on commit 5b59bf5

Please sign in to comment.