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

Offer to install kubectl #634

Merged
Merged
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
28 changes: 28 additions & 0 deletions src/warnet/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,31 @@
"checksum": "fc370a291ed926da5e77acf42006de48e7fd5ff94d20c3f6aa10c04fea66e53c",
},
]


# Kubectl binary
KUBECTL_BINARY_NAME = "kubectl"
KUBECTL_BLESSED_VERSION = "v1.31.1"
KUBECTL_DOWNLOAD_URL_STUB = f"https://dl.k8s.io/release/{KUBECTL_BLESSED_VERSION}/bin"
KUBECTL_BLESSED_NAME_AND_CHECKSUMS = [
{
"system": "linux",
"arch": "amd64",
"checksum": "57b514a7facce4ee62c93b8dc21fda8cf62ef3fed22e44ffc9d167eab843b2ae",
},
{
"system": "linux",
"arch": "arm64",
"checksum": "3af2451191e27ecd4ac46bb7f945f76b71e934d54604ca3ffc7fe6f5dd123edb",
},
{
"system": "darwin",
"arch": "amd64",
"checksum": "4b86d3fb8dee8dd61f341572f1ba13c1030d493f4dc1b4831476f61f3cbb77d0",
},
{
"system": "darwin",
"arch": "arm64",
"checksum": "08909b92e62004f4f1222dfd39214085383ea368bdd15c762939469c23484634",
},
]
115 changes: 103 additions & 12 deletions src/warnet/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
HELM_BLESSED_NAME_AND_CHECKSUMS,
HELM_BLESSED_VERSION,
HELM_DOWNLOAD_URL_STUB,
KUBECTL_BINARY_NAME,
KUBECTL_BLESSED_NAME_AND_CHECKSUMS,
KUBECTL_BLESSED_VERSION,
KUBECTL_DOWNLOAD_URL_STUB,
)
from .graph import inquirer_create_network
from .network import copy_network_defaults, copy_scenario_defaults
Expand Down Expand Up @@ -147,7 +151,7 @@ def is_docker_desktop_running() -> tuple[bool, str]:
except FileNotFoundError as err:
return False, str(err)

def is_kubectl_installed() -> tuple[bool, str]:
def is_kubectl_installed_and_offer_if_not() -> tuple[bool, str]:
try:
version_result = subprocess.run(
["kubectl", "version", "--client"],
Expand All @@ -163,8 +167,30 @@ def is_kubectl_installed() -> tuple[bool, str]:
return True, location_result.stdout.strip()
else:
return False, ""
except FileNotFoundError as err:
return False, str(err)
except FileNotFoundError:
print()
kubectl_answer = inquirer.prompt(
[
inquirer.Confirm(
"install_kubectl",
message=click.style(
"Would you like Warnet to install Kubectl into your virtual environment?",
fg="blue",
bold=True,
),
default=True,
),
]
)
if kubectl_answer is None:
msg = "Setup cancelled by user."
click.secho(msg, fg="yellow")
return False, msg
if kubectl_answer["install_kubectl"]:
click.secho(" Installing Kubectl...", fg="yellow", bold=True)
install_kubectl_rootlessly_to_venv()
return is_kubectl_installed_and_offer_if_not()
return False, "Please install Kubectl."

def is_helm_installed_and_offer_if_not() -> tuple[bool, str]:
try:
Expand Down Expand Up @@ -246,7 +272,7 @@ def check_installation(tool_info: ToolInfo) -> ToolStatus:
)
kubectl_info = ToolInfo(
tool_name="Kubectl",
is_installed_func=is_kubectl_installed,
is_installed_func=is_kubectl_installed_and_offer_if_not,
install_instruction="Install kubectl.",
install_url="https://kubernetes.io/docs/tasks/tools/install-kubectl/",
)
Expand Down Expand Up @@ -446,7 +472,23 @@ def query_arch_from_uname(arch: str) -> Optional[str]:
return None


def write_blessed_checksum(helm_filename: str, dest_path: str):
def write_blessed_kubectl_checksum(system: str, arch: str, dest_path: str):
checksum = next(
(
b["checksum"]
for b in KUBECTL_BLESSED_NAME_AND_CHECKSUMS
if b["system"] == system and b["arch"] == arch
),
None,
)
if checksum:
with open(dest_path, "w") as f:
f.write(checksum)
else:
click.secho("Could not find a matching kubectl binary and checksum", fg="red")


def write_blessed_helm_checksum(helm_filename: str, dest_path: str):
checksum = next(
(b["checksum"] for b in HELM_BLESSED_NAME_AND_CHECKSUMS if b["name"] == helm_filename), None
)
Expand All @@ -472,12 +514,12 @@ def verify_checksum(file_path, checksum_path):
click.secho(" Checksum verified.", fg="blue")


def install_helm_to_venv(helm_bin_path):
def install_to_venv(bin_path, binary_name):
venv_bin_dir = os.path.join(sys.prefix, "bin")
helm_dst_path = os.path.join(venv_bin_dir, HELM_BINARY_NAME)
shutil.move(helm_bin_path, helm_dst_path)
os.chmod(helm_dst_path, 0o755)
click.secho(f" {HELM_BINARY_NAME} installed into {helm_dst_path}", fg="blue")
dst_path = os.path.join(venv_bin_dir, binary_name)
shutil.move(bin_path, dst_path)
os.chmod(dst_path, 0o755)
click.secho(f" {binary_name} installed into {dst_path}", fg="blue")


def install_helm_rootlessly_to_venv():
Expand Down Expand Up @@ -512,14 +554,14 @@ def install_helm_rootlessly_to_venv():
checksum_path = os.path.join(temp_dir, f"{helm_filename}.sha256")

download_file(helm_url, helm_archive_path)
write_blessed_checksum(helm_filename, checksum_path)
write_blessed_helm_checksum(helm_filename, checksum_path)
verify_checksum(helm_archive_path, checksum_path)

# Extract Helm and install it in the virtual environment's bin folder
with tarfile.open(helm_archive_path, "r:gz") as tar:
tar.extractall(path=temp_dir)
helm_bin_path = os.path.join(temp_dir, os_name + "-" + arch, HELM_BINARY_NAME)
install_helm_to_venv(helm_bin_path)
install_to_venv(helm_bin_path, HELM_BINARY_NAME)

click.secho(
f" {HELM_BINARY_NAME} {version} installed successfully to your virtual environment!\n",
Expand All @@ -529,3 +571,52 @@ def install_helm_rootlessly_to_venv():
except Exception as e:
click.secho(f"Error: {e}\nCould not install helm.", fg="yellow")
sys.exit(1)


def install_kubectl_rootlessly_to_venv():
if not is_in_virtualenv():
click.secho(
"Error: You are not in a virtual environment. Please activate a virtual environment and try again.",
fg="yellow",
)
sys.exit(1)

os_name = get_os_name_for_helm()
if os_name is None:
click.secho(
"Error: Could not determine the operating system of this computer.", fg="yellow"
)
sys.exit(1)

uname_arch = os.uname().machine
arch = query_arch_from_uname(uname_arch)
if arch not in ["arm64", "amd64"]:
click.secho(f"No Kubectl binary candidate for arch: {uname_arch}", fg="red")
sys.exit(1)

uname_sys = os.uname().sysname.lower()
if uname_sys not in ["linux", "darwin"]:
click.secho(f"The following system is not supported: {uname_sys}", fg="red")
sys.exit(1)

kubectl_url = f"{KUBECTL_DOWNLOAD_URL_STUB}/{uname_sys}/{arch}/{KUBECTL_BINARY_NAME}"

try:
with tempfile.TemporaryDirectory() as temp_dir:
binary_path = os.path.join(temp_dir, KUBECTL_BINARY_NAME)
checksum_path = os.path.join(temp_dir, f"{KUBECTL_BINARY_NAME}.sha256")

download_file(kubectl_url, binary_path)
write_blessed_kubectl_checksum(uname_sys, arch, checksum_path)
verify_checksum(binary_path, checksum_path)

install_to_venv(binary_path, KUBECTL_BINARY_NAME)

click.secho(
f" {KUBECTL_BINARY_NAME} {KUBECTL_BLESSED_VERSION} installed successfully to your virtual environment!\n",
fg="blue",
)

except Exception as e:
click.secho(f"Error: {e}\nCould not install helm.", fg="yellow")
sys.exit(1)