wip
This commit is contained in:
parent
9737746923
commit
378744632f
7 changed files with 208 additions and 173 deletions
|
|
@ -11,7 +11,7 @@ basics = [
|
|||
"coreutils",
|
||||
]
|
||||
pretty = [
|
||||
*basics
|
||||
*basics,
|
||||
"starship",
|
||||
"atuin",
|
||||
"bash",
|
||||
|
|
@ -21,7 +21,7 @@ pretty = [
|
|||
"eza",
|
||||
]
|
||||
networking = [
|
||||
*basics
|
||||
*basics,
|
||||
"mtr",
|
||||
"bind-tools",
|
||||
"aws-cli",
|
||||
|
|
@ -34,7 +34,7 @@ networking = [
|
|||
]
|
||||
|
||||
database = [
|
||||
*basics
|
||||
*basics,
|
||||
"sqlite",
|
||||
"sqlite-dev",
|
||||
"sqlite-libs",
|
||||
|
|
@ -46,7 +46,7 @@ database = [
|
|||
]
|
||||
|
||||
storage = [
|
||||
*basics
|
||||
*basics,
|
||||
"ncdu",
|
||||
"dust",
|
||||
"file",
|
||||
|
|
@ -58,7 +58,7 @@ storage = [
|
|||
]
|
||||
|
||||
search = [
|
||||
*basics
|
||||
*basics,
|
||||
"ripgrep",
|
||||
"fd",
|
||||
"fzf",
|
||||
|
|
@ -66,7 +66,7 @@ search = [
|
|||
]
|
||||
|
||||
monitoring = [
|
||||
*basics
|
||||
*basics,
|
||||
"htop",
|
||||
"bottom",
|
||||
"mtr",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
from krayt import __version__
|
||||
from krayt.cli.create import app as create_app
|
||||
from krayt.cli.bundles import app as bundles_app
|
||||
from krayt.cli.pod import app as pod_app, create, exec, logs
|
||||
from krayt.cli.templates import app as templates_app
|
||||
from typer import Typer
|
||||
|
||||
app = Typer()
|
||||
|
||||
app.add_typer(templates_app, name="templates")
|
||||
app.add_typer(create_app, name="create")
|
||||
app.add_typer(templates_app, name="templates", no_args_is_help=True)
|
||||
app.add_typer(pod_app, name="pod", no_args_is_help=True)
|
||||
app.command(name="create")(create)
|
||||
app.command(name="c")(create)
|
||||
app.command(name="exec")(exec)
|
||||
app.command(name="logs")(logs)
|
||||
app.add_typer(bundles_app, name="bundles", no_args_is_help=True)
|
||||
|
||||
|
||||
@app.command()
|
||||
|
|
|
|||
25
krayt/cli/bundles.py
Normal file
25
krayt/cli/bundles.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from krayt import bundles
|
||||
import typer
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
|
||||
@app.command()
|
||||
def list(
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose",
|
||||
"-v",
|
||||
help="Verbose output",
|
||||
),
|
||||
):
|
||||
"""List available bundles"""
|
||||
typer.echo("Available bundles:")
|
||||
# get all variables from bundles
|
||||
for bundle in bundles.__dict__.keys():
|
||||
if bundle.startswith("__"):
|
||||
continue
|
||||
typer.echo(bundle)
|
||||
if verbose:
|
||||
for package in bundles.__dict__[bundle]:
|
||||
typer.echo(f" - {package}")
|
||||
|
|
@ -1,16 +1,13 @@
|
|||
from iterfzf import iterfzf
|
||||
import iterfzf
|
||||
from krayt.templates import env
|
||||
from kubernetes import client, config
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import time
|
||||
import typer
|
||||
from typing import Any, List, Optional
|
||||
import yaml
|
||||
|
||||
KRAYT_VERSION = "NIGHTLY"
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
app = typer.Typer()
|
||||
|
|
@ -92,7 +89,15 @@ def fuzzy_select(items):
|
|||
|
||||
# Use fzf for selection
|
||||
try:
|
||||
selected = iterfzf(formatted_items)
|
||||
# selected = inquirer.fuzzy(
|
||||
# message="Select a pod to clone:", choices=formatted_items
|
||||
# ).execute()
|
||||
|
||||
selected = iterfzf.iterfzf(
|
||||
formatted_items,
|
||||
prompt="Select a pod to clone:",
|
||||
preview='''kubectl describe pod "$(echo {} | awk -F'[(|)]' '{gsub(/\x1b\[[0-9;]*m/, "", $1); print $1}' | xargs)" -n "$(echo {} | awk -F'[(|)]' '{gsub(/\x1b\[[0-9;]*m/, "", $2); print $2}' | xargs)"''',
|
||||
)
|
||||
if not selected:
|
||||
return None, None
|
||||
|
||||
|
|
@ -239,83 +244,6 @@ def get_env_vars_and_secret_volumes(api, namespace: str):
|
|||
return env_vars, volumes
|
||||
|
||||
|
||||
def get_init_scripts():
|
||||
"""Get the contents of init scripts to be run in the pod"""
|
||||
init_dir = Path.home() / ".config" / "krayt" / "init.d"
|
||||
if not init_dir.exists():
|
||||
logging.debug("No init.d directory found at %s", init_dir)
|
||||
return ""
|
||||
|
||||
scripts = sorted(init_dir.glob("*.sh"))
|
||||
if not scripts:
|
||||
logging.debug("No init scripts found in %s", init_dir)
|
||||
return ""
|
||||
|
||||
# Create a combined script that will run all init scripts
|
||||
init_script = "#!/bin/bash\n\n"
|
||||
init_script += "exec 2>&1 # Redirect stderr to stdout for proper logging\n"
|
||||
init_script += "set -e # Exit on error\n\n"
|
||||
init_script += "echo 'Running initialization scripts...' | tee /tmp/init.log\n\n"
|
||||
init_script += "mkdir -p /tmp/init.d\n\n" # Create directory once at the start
|
||||
|
||||
for script in scripts:
|
||||
try:
|
||||
with open(script, "r") as f:
|
||||
script_content = f.read()
|
||||
if not script_content.strip():
|
||||
logging.debug("Skipping empty script %s", script)
|
||||
continue
|
||||
|
||||
# Use a unique heredoc delimiter for each script to avoid nesting issues
|
||||
delimiter = f"EOF_SCRIPT_{script.stem.upper()}"
|
||||
|
||||
init_script += (
|
||||
f"echo '=== Running {script.name} ===' | tee -a /tmp/init.log\n"
|
||||
)
|
||||
init_script += f"cat > /tmp/init.d/{script.name} << '{delimiter}'\n"
|
||||
init_script += script_content
|
||||
if not script_content.endswith("\n"):
|
||||
init_script += "\n"
|
||||
init_script += f"{delimiter}\n"
|
||||
init_script += f"chmod +x /tmp/init.d/{script.name}\n"
|
||||
init_script += f'cd /tmp/init.d && ./{script.name} 2>&1 | tee -a /tmp/init.log || {{ echo "Failed to run {script.name}"; exit 1; }}\n'
|
||||
init_script += (
|
||||
f"echo '=== Finished {script.name} ===' | tee -a /tmp/init.log\n\n"
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load init script {script}: {e}")
|
||||
|
||||
init_script += "echo 'Initialization scripts complete.' | tee -a /tmp/init.log\n"
|
||||
return init_script
|
||||
|
||||
|
||||
def get_motd_script(mount_info, pvc_info):
|
||||
"""Generate the MOTD script with proper escaping"""
|
||||
return f"""
|
||||
# Create MOTD
|
||||
cat << EOF > /etc/motd
|
||||
====================================
|
||||
Krayt Dragon's Lair
|
||||
A safe haven for volume inspection
|
||||
====================================
|
||||
|
||||
"Inside every volume lies a pearl of wisdom waiting to be discovered."
|
||||
|
||||
Mounted Volumes:
|
||||
$(echo "{",".join(mount_info)}" | tr ',' '\\n' | sed 's/^/- /')
|
||||
|
||||
Persistent Volume Claims:
|
||||
$(echo "{",".join(pvc_info)}" | tr ',' '\\n' | sed 's/^/- /')
|
||||
|
||||
Mounted Secrets:
|
||||
$(for d in /mnt/secrets/*; do if [ -d "$d" ]; then echo "- $(basename $d)"; fi; done)
|
||||
|
||||
Init Script Status:
|
||||
$(if [ -f /tmp/init.log ]; then echo "View initialization log at /tmp/init.log"; fi)
|
||||
EOF
|
||||
"""
|
||||
|
||||
|
||||
def create_inspector_job(
|
||||
api,
|
||||
namespace: str,
|
||||
|
|
@ -418,6 +346,7 @@ def create_inspector_job(
|
|||
"annotations": {"pvcs": ",".join(pvc_info) if pvc_info else "none"},
|
||||
},
|
||||
"spec": {
|
||||
"ttlSecondsAfterFinished": 600,
|
||||
"template": {
|
||||
"metadata": {"labels": {"app": "krayt"}},
|
||||
"spec": {
|
||||
|
|
@ -459,24 +388,6 @@ PROTECTED_NAMESPACES = {
|
|||
}
|
||||
|
||||
|
||||
def load_init_scripts():
|
||||
"""Load and execute initialization scripts from ~/.config/krayt/scripts/"""
|
||||
init_dir = Path.home() / ".config" / "krayt" / "scripts"
|
||||
if not init_dir.exists():
|
||||
return
|
||||
|
||||
# Sort scripts to ensure consistent execution order
|
||||
scripts = sorted(init_dir.glob("*.py"))
|
||||
|
||||
for script in scripts:
|
||||
try:
|
||||
with open(script, "r") as f:
|
||||
exec(f.read(), globals())
|
||||
logging.debug(f"Loaded init script: {script}")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load init script {script}: {e}")
|
||||
|
||||
|
||||
def setup_environment():
|
||||
"""Set up the environment with proxy settings and other configurations"""
|
||||
# Load environment variables for proxies
|
||||
|
|
@ -657,8 +568,16 @@ def clean(
|
|||
def create(
|
||||
namespace: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--namespace",
|
||||
"-n",
|
||||
help="Kubernetes namespace. If not specified, will search for pods across all namespaces.",
|
||||
),
|
||||
clone: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--clone",
|
||||
"-c",
|
||||
help="Clone an existing pod",
|
||||
),
|
||||
image: str = typer.Option(
|
||||
"alpine:latest",
|
||||
"--image",
|
||||
|
|
@ -670,6 +589,39 @@ def create(
|
|||
"--imagepullsecret",
|
||||
help="Name of the image pull secret to use for pulling private images",
|
||||
),
|
||||
additional_packages: Optional[List[str]] = typer.Option(
|
||||
None,
|
||||
"--additional-packages",
|
||||
"-ap",
|
||||
help="additional packages to install in the inspector pod",
|
||||
),
|
||||
additional_package_bundles: Optional[List[str]] = typer.Option(
|
||||
None,
|
||||
"--additional-package-bundles",
|
||||
"-ab",
|
||||
help="additional packages to install in the inspector pod",
|
||||
),
|
||||
pre_init_scripts: Optional[List[str]] = typer.Option(
|
||||
None,
|
||||
"--pre-init-scripts",
|
||||
help="additional scripts to execute at the end of container initialization",
|
||||
),
|
||||
post_init_scripts: Optional[List[str]] = typer.Option(
|
||||
None,
|
||||
"--post-init-scripts",
|
||||
"--init-scripts",
|
||||
help="additional scripts to execute at the start of container initialization",
|
||||
),
|
||||
pre_init_hooks: Optional[List[str]] = typer.Option(
|
||||
None,
|
||||
"--pre-init-hooks",
|
||||
help="additional hooks to execute at the end of container initialization",
|
||||
),
|
||||
post_init_hooks: Optional[List[str]] = typer.Option(
|
||||
None,
|
||||
"--post-init-hooks",
|
||||
help="additional hooks to execute at the start of container initialization",
|
||||
),
|
||||
):
|
||||
"""
|
||||
Krack open a Krayt dragon! Create an inspector pod to explore what's inside your volumes.
|
||||
|
|
@ -677,15 +629,36 @@ def create(
|
|||
The inspector will be created in the same namespace as the selected pod.
|
||||
"""
|
||||
# For create, we want to list all pods, not just Krayt pods
|
||||
selected_namespace = None
|
||||
selected_pod = None
|
||||
typer.echo(namespace)
|
||||
typer.echo(clone)
|
||||
|
||||
if namespace is None and clone is not None and "/" in clone:
|
||||
selected_namespace, selected_pod = clone.split("/", 1)
|
||||
elif namespace is not None and clone is not None:
|
||||
selected_namespace = namespace
|
||||
selected_pod = clone
|
||||
|
||||
pods = get_pods(namespace, label_selector=None)
|
||||
if not pods:
|
||||
typer.echo("No pods found.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
selected_pod, selected_namespace = fuzzy_select(pods)
|
||||
if not selected_pod:
|
||||
typer.echo("No pod selected.")
|
||||
raise typer.Exit(1)
|
||||
if selected_pod not in (p[0] for p in pods) or selected_pod is None:
|
||||
if selected_pod is not None:
|
||||
pods = [p for p in pods if selected_pod in p[0]]
|
||||
if len(pods) == 1:
|
||||
selected_pod, selected_namespace = pods[0]
|
||||
else:
|
||||
selected_pod, selected_namespace = fuzzy_select(pods)
|
||||
if not selected_pod:
|
||||
typer.echo("No pod selected.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
typer.echo(f"Selected pod exists: {selected_pod in (p[0] for p in pods)}")
|
||||
typer.echo(f"Selected pod: {selected_pod} ({selected_namespace})")
|
||||
raise typer.Exit(1)
|
||||
|
||||
pod_spec = get_pod_spec(selected_pod, selected_namespace)
|
||||
volume_mounts, volumes = get_pod_volumes_and_mounts(pod_spec)
|
||||
|
|
@ -758,9 +731,19 @@ def logs(
|
|||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@app.command("list")
|
||||
def list_pods():
|
||||
pods = get_pods()
|
||||
if not pods:
|
||||
typer.echo("No pods found.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
for pod, namespace in pods:
|
||||
typer.echo(f"{pod} ({namespace})")
|
||||
|
||||
|
||||
def main():
|
||||
setup_environment()
|
||||
load_init_scripts()
|
||||
app()
|
||||
|
||||
|
||||
|
|
@ -2,7 +2,25 @@ from krayt.templates import env
|
|||
import typer
|
||||
from typing import List, Optional
|
||||
|
||||
app = typer.Typer()
|
||||
# app = typer.Typer()
|
||||
app = typer.Typer(
|
||||
context_settings={
|
||||
"auto_envvar_prefix": "KRAYT",
|
||||
"help_option_names": ["-h", "--help"],
|
||||
"show_default": True,
|
||||
"allow_interspersed_args": True,
|
||||
"ignore_unknown_options": False,
|
||||
"max_content_width": None,
|
||||
"suggest_command": True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def list():
|
||||
typer.echo("Available templates:")
|
||||
for template in env.list_templates():
|
||||
typer.echo(template)
|
||||
|
||||
|
||||
@app.command()
|
||||
|
|
|
|||
116
krayt/package.py
116
krayt/package.py
|
|
@ -40,6 +40,7 @@ class Package(BaseModel):
|
|||
"uv",
|
||||
"i",
|
||||
"curlsh",
|
||||
"curlbash",
|
||||
"brew",
|
||||
"cargo",
|
||||
"pipx",
|
||||
|
|
@ -49,8 +50,10 @@ class Package(BaseModel):
|
|||
],
|
||||
BeforeValidator(validate_kind),
|
||||
] = "system"
|
||||
dependencies: Optional[List[str]] = None
|
||||
value: str
|
||||
dependencies: Optional[List["Package"]] = None
|
||||
pre_install_hook: Optional[str] = None
|
||||
post_install_hook: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw: str) -> "Package":
|
||||
|
|
@ -67,52 +70,37 @@ class Package(BaseModel):
|
|||
def validate_dependencies(self) -> Self:
|
||||
if self.dependencies:
|
||||
return self
|
||||
else:
|
||||
if self.kind == "system":
|
||||
return self
|
||||
dependencies = []
|
||||
if self.kind in ["uv", "i", "installer", "curlbash", "curlsh", "gh"]:
|
||||
dependencies.extend(
|
||||
[
|
||||
Package.from_raw("curl"),
|
||||
]
|
||||
)
|
||||
if self.kind == "brew":
|
||||
dependencies.extend(
|
||||
[
|
||||
Package.from_raw("brew"),
|
||||
Package.from_raw("git"),
|
||||
]
|
||||
)
|
||||
if self.kind == "cargo":
|
||||
dependencies.extend(
|
||||
[
|
||||
Package.from_raw("cargo"),
|
||||
]
|
||||
)
|
||||
if self.kind == "pipx":
|
||||
dependencies.extend(
|
||||
[
|
||||
Package.from_raw("pipx"),
|
||||
]
|
||||
)
|
||||
if self.kind == "npm":
|
||||
dependencies.extend(
|
||||
[
|
||||
Package.from_raw("npm"),
|
||||
]
|
||||
)
|
||||
if self.kind == "go":
|
||||
dependencies.extend(
|
||||
[
|
||||
Package.from_raw("go"),
|
||||
]
|
||||
)
|
||||
self.dependencies = dependencies
|
||||
return self
|
||||
dependencies = []
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.kind}:{self.value}" if self.kind != "system" else self.value
|
||||
if self.kind in ["uv", "i", "installer", "curlbash", "curlsh", "gh"]:
|
||||
dependencies.append(Package.from_raw("curl"))
|
||||
if self.kind == "brew":
|
||||
dependencies.append(Package.from_raw("git"))
|
||||
dependencies.append(Package.from_raw("curl"))
|
||||
self.pre_install_hook = "NONINTERACTIVE=1"
|
||||
self.post_install_hook = """
|
||||
# Setup Homebrew PATH
|
||||
if [ -f /home/linuxbrew/.linuxbrew/bin/brew ]; then
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
elif [ -f /opt/homebrew/bin/brew ]; then
|
||||
eval "$(/opt/homebrew/.linuxbrew/bin/brew shellenv)"
|
||||
elif [ -f /usr/local/bin/brew ]; then
|
||||
eval "$(/usr/local/bin/brew shellenv)"
|
||||
else
|
||||
echo "⚠️ Brew installed but binary location unknown."
|
||||
fi
|
||||
"""
|
||||
if self.kind == "cargo":
|
||||
dependencies.append(Package.from_raw("cargo"))
|
||||
if self.kind == "pipx":
|
||||
dependencies.append(Package.from_raw("pipx"))
|
||||
if self.kind == "npm":
|
||||
dependencies.append(Package.from_raw("npm"))
|
||||
if self.kind == "go":
|
||||
dependencies.append(Package.from_raw("go"))
|
||||
|
||||
self.dependencies = dependencies
|
||||
return self
|
||||
|
||||
def is_system(self) -> bool:
|
||||
return self.kind == "system"
|
||||
|
|
@ -121,29 +109,36 @@ class Package(BaseModel):
|
|||
"""
|
||||
Generate the bash install command snippet for this package.
|
||||
"""
|
||||
cmd = ""
|
||||
if self.kind == "system":
|
||||
return f"detect_package_manager_and_install {self.value}"
|
||||
cmd = f"detect_package_manager_and_install {self.value}"
|
||||
elif self.kind == "uv":
|
||||
return f"uv tool install {self.value}"
|
||||
cmd = f"uv tool install {self.value}"
|
||||
elif self.kind in ["i", "installer", "gh"]:
|
||||
return f"curl -fsSL https://i.jpillora.com/{self.value} | sh"
|
||||
cmd = f"curl -fsSL https://i.jpillora.com/{self.value} | sh"
|
||||
elif self.kind == "curlsh":
|
||||
return f"curl -fsSL {self.value} | sh"
|
||||
cmd = f"curl -fsSL {self.value} | sh"
|
||||
elif self.kind == "curlbash":
|
||||
return f"curl -fsSL {self.value} | bash"
|
||||
cmd = f"curl -fsSL {self.value} | bash"
|
||||
elif self.kind == "brew":
|
||||
return f"brew install {self.value}"
|
||||
cmd = "curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash"
|
||||
elif self.kind == "cargo":
|
||||
return f"cargo install {self.value}"
|
||||
cmd = f"cargo install {self.value}"
|
||||
elif self.kind == "pipx":
|
||||
return f"pipx install {self.value}"
|
||||
cmd = f"pipx install {self.value}"
|
||||
elif self.kind == "npm":
|
||||
return f"npm install -g {self.value}"
|
||||
cmd = f"npm install -g {self.value}"
|
||||
elif self.kind == "go":
|
||||
return f"go install {self.value}@latest"
|
||||
cmd = f"go install {self.value}@latest"
|
||||
else:
|
||||
raise ValueError(f"Unknown install method for kind={self.kind}")
|
||||
|
||||
# Add pre-install hook if necessary
|
||||
if self.pre_install_hook:
|
||||
return f"{self.pre_install_hook} {cmd}"
|
||||
else:
|
||||
return cmd
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raw_inputs = [
|
||||
|
|
@ -163,4 +158,11 @@ if __name__ == "__main__":
|
|||
[dependency.install_command() for dependency in package.dependencies]
|
||||
)
|
||||
installs = [package.install_command() for package in packages]
|
||||
print("\n".join(install for install in unique_everseen([*dependencies, *installs])))
|
||||
post_hooks = []
|
||||
for package in packages:
|
||||
if package.post_install_hook:
|
||||
post_hooks.append(package.post_install_hook.strip())
|
||||
|
||||
# Final full script
|
||||
full_script = list(unique_everseen([*dependencies, *installs, *post_hooks]))
|
||||
print("\n".join(full_script))
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ classifiers = [
|
|||
dependencies = [
|
||||
"typer",
|
||||
"kubernetes",
|
||||
"InquirerPy",
|
||||
"inquirerPy",
|
||||
"inquirer",
|
||||
"jinja2",
|
||||
"iterfzf",
|
||||
"pydantic",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue