Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] remote-debug #126

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions jhack/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def main():
from jhack.utils.tail_charms import tail_events
from jhack.utils.unbork_juju import unbork_juju
from jhack.utils.unleash import vanity
from jhack.utils.remote_debug import remote_debug

utils = typer.Typer(name="utils", help="Charming utilities.")
utils.command(name="sync", no_args_is_help=True)(sync_deployed_charm)
Expand All @@ -70,6 +71,7 @@ def main():
utils.command(name="fire", no_args_is_help=True)(simulate_event)
utils.command(name="pull-cmr", no_args_is_help=True)(integrate.cmr)
utils.command(name="print-env")(print_env)
utils.command(name="remote-debug", no_args_is_help=True)(remote_debug)

jinx = typer.Typer(
name="jinx",
Expand Down
120 changes: 120 additions & 0 deletions jhack/utils/remote_debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import shlex
import tempfile
from pathlib import Path

import typer

from jhack.helpers import get_substrate, JPopen
from jhack.logger import logger as jhack_logger

logger = jhack_logger.getChild("endpoints")


def _upload_text(text: str, target: str, remote_path: str):
# use a prefix that juju can 'see' despite the snap confinement!
with tempfile.NamedTemporaryFile(
dir=Path("~/.local/share/juju/").expanduser(), prefix=".jhack_tmp_"
) as f:
Path(f.name).write_text(text)
# upload script to charm
proc = JPopen(shlex.split(f"juju scp {f.name} {target}:./{remote_path}"))
proc.wait()

if proc.returncode != 0:
exit(
f"failed uploading to {remote_path};"
f"\n stdout={proc.stdout.read()},"
f"\n stderr={proc.stderr.read()}"
)


def _setup_remote_debug(
target: str, listen_address: str = "localhost:5678", debugpy_version: str = "1.8.0"
):
sanitized_unit_name = target.replace("/", "-")
install_script_name = "install_debugpy.sh"

if get_substrate() == "k8s":
logger.debug("detected kubernetes env")

install_debugpy_script = f"""#!/bin/bash
f.name
cd ./agents/unit-{sanitized_unit_name}/charm/venv
curl -LO https://github.com/microsoft/debugpy/archive/refs/tags/v{debugpy_version}.zip
apt update -y
apt install unzip -y
unzip ./v{debugpy_version}.zip
rm ./v{debugpy_version}.zip
mv ./debugpy-{debugpy_version}/src/debugpy/ ./debugpy
"""

logger.info("uploading install script...")
_upload_text(install_debugpy_script, target, f"./{install_script_name}")

logger.info("installing... (this may take a little while)")

# need to make it executable, else juju agent might have trouble later
chmod_x_proc = JPopen(
shlex.split(f"juju ssh {target} chmod +x ./{install_script_name}")
)
chmod_x_proc.wait()

proc = JPopen(shlex.split(f"juju ssh {target} bash ./{install_script_name}"))
proc.wait()
# proc = JPopen(shlex.split(f"juju ssh {target} ./{install_script_name}"))
# proc.wait()

if proc.returncode != 0:
exit(
f"failed executing install script \n stdout={proc.stdout.read()} \n stderr={proc.stderr.read()}",
)

else:
raise NotImplementedError("not implemented yet for lxd")

# let's patch dispatch!
logger.info("patching dispatch script... (dis-patch or dat-patch?)")
new_dispatch_script = """#!/bin/sh
# Dispatch injected by jhack's remote-debug tool

JUJU_DISPATCH_PATH="${JUJU_DISPATCH_PATH:-$0}" PYTHONPATH=lib:venv:$PYTHONPATH \
python3 -m debugpy --listen {listen_address} ./src/charm.py
""".replace(
"listen_address", listen_address
)

_upload_text(
new_dispatch_script,
target,
"./agents/unit-{sanitized_unit_name}/charm/dispatch",
)

logger.info("debugpy installed on dispatch.")


def remote_debug(
target: str = typer.Argument(..., help="Juju unit name you want to target."),
debugpy_version: str = typer.Option(
"1.8.0",
"--debugpy-version",
"-d",
help="Debugpy version to install. See on https://github.com/microsoft/debugpy/releases "
"which tags are available.",
),
listen_address: str = typer.Option(
"localhost:5678",
"--listen-address",
"-l",
help="host:port at which the debugpy server will be listening. Has to match your local "
"debugger client configuration.",
),
):
"""Set up a remote debugging server (debugpy) on a juju unit.

Once this is done, you can connect an interactive debugging session from (for example) vscode.
"""
_setup_remote_debug(target, listen_address, debugpy_version)


if __name__ == "__main__":
_setup_remote_debug("traefik/0")