1
+ import hashlib
1
2
import os
2
3
import platform
4
+ import shutil
3
5
import subprocess
4
6
import sys
7
+ import tarfile
8
+ import tempfile
5
9
from dataclasses import dataclass
6
10
from enum import Enum , auto
7
11
from pathlib import Path
8
- from typing import Callable
12
+ from typing import Callable , Optional
9
13
10
14
import click
11
15
import inquirer
16
+ import requests
12
17
18
+ from .constants import HELM_BINARY_NAME , HELM_DOWNLOAD_URL_STUB , HELM_LATEST_URL
13
19
from .graph import inquirer_create_network
14
20
from .network import copy_network_defaults , copy_scenario_defaults
15
21
@@ -19,6 +25,7 @@ def setup():
19
25
"""Setup warnet"""
20
26
21
27
class ToolStatus (Enum ):
28
+ NeedsHelm = auto ()
22
29
Satisfied = auto ()
23
30
Unsatisfied = auto ()
24
31
@@ -155,7 +162,7 @@ def is_kubectl_installed() -> tuple[bool, str]:
155
162
except FileNotFoundError as err :
156
163
return False , str (err )
157
164
158
- def is_helm_installed () -> tuple [bool , str ]:
165
+ def is_helm_installed_and_offer_if_not () -> tuple [bool , str ]:
159
166
try :
160
167
version_result = subprocess .run (["helm" , "version" ], capture_output = True , text = True )
161
168
location_result = subprocess .run (
@@ -167,8 +174,31 @@ def is_helm_installed() -> tuple[bool, str]:
167
174
return version_result .returncode == 0 , location_result .stdout .strip ()
168
175
else :
169
176
return False , ""
170
- except FileNotFoundError as err :
171
- return False , str (err )
177
+
178
+ except FileNotFoundError :
179
+ print ()
180
+ helm_answer = inquirer .prompt (
181
+ [
182
+ inquirer .Confirm (
183
+ "install_helm" ,
184
+ message = click .style (
185
+ "Would you like to use Warnet's downloader to install Helm into your virtual environment?" ,
186
+ fg = "blue" ,
187
+ bold = True ,
188
+ ),
189
+ default = True ,
190
+ ),
191
+ ]
192
+ )
193
+ if helm_answer is None :
194
+ msg = "Setup cancelled by user."
195
+ click .secho (msg , fg = "yellow" )
196
+ return False , msg
197
+ if helm_answer ["install_helm" ]:
198
+ click .secho (" Installing Helm..." , fg = "yellow" , bold = True )
199
+ install_helm_rootlessly_to_venv ()
200
+ return is_helm_installed_and_offer_if_not ()
201
+ return False , "Please install Helm."
172
202
173
203
def check_installation (tool_info : ToolInfo ) -> ToolStatus :
174
204
has_good_version , location = tool_info .is_installed_func ()
@@ -218,8 +248,8 @@ def check_installation(tool_info: ToolInfo) -> ToolStatus:
218
248
)
219
249
helm_info = ToolInfo (
220
250
tool_name = "Helm" ,
221
- is_installed_func = is_helm_installed ,
222
- install_instruction = "Install Helm from Helm's official site." ,
251
+ is_installed_func = is_helm_installed_and_offer_if_not ,
252
+ install_instruction = "Install Helm from Helm's official site, or rootlessly install Helm using Warnet's downloader when prompted ." ,
223
253
install_url = "https://helm.sh/docs/intro/install/" ,
224
254
)
225
255
minikube_info = ToolInfo (
@@ -361,3 +391,120 @@ def init():
361
391
"""Initialize a warnet project in the current directory"""
362
392
current_dir = Path .cwd ()
363
393
new_internal (directory = current_dir , from_init = True )
394
+
395
+
396
+ def get_os_name_for_helm () -> Optional [str ]:
397
+ """Return a short operating system name suitable for downloading a helm binary."""
398
+ uname_sys = platform .system ().lower ()
399
+ if "linux" in uname_sys :
400
+ return "linux"
401
+ elif uname_sys == "darwin" :
402
+ return "darwin"
403
+ elif "win" in uname_sys :
404
+ return "windows"
405
+ return None
406
+
407
+
408
+ def is_in_virtualenv () -> bool :
409
+ """Check if the user is in a virtual environment."""
410
+ return hasattr (sys , "real_prefix" ) or (
411
+ hasattr (sys , "base_prefix" ) and sys .base_prefix != sys .prefix
412
+ )
413
+
414
+
415
+ def download_file (url , destination ):
416
+ click .secho (f" Downloading { url } " , fg = "blue" )
417
+ response = requests .get (url , stream = True )
418
+ if response .status_code == 200 :
419
+ with open (destination , "wb" ) as f :
420
+ for chunk in response .iter_content (1024 ):
421
+ f .write (chunk )
422
+ else :
423
+ raise Exception (f"Failed to download { url } (status code { response .status_code } )" )
424
+
425
+
426
+ def get_latest_version_of_helm () -> Optional [str ]:
427
+ response = requests .get (HELM_LATEST_URL )
428
+ if response .status_code == 200 :
429
+ return response .text .strip ()
430
+ else :
431
+ return None
432
+
433
+
434
+ def verify_checksum (file_path , checksum_path ):
435
+ click .secho (" Verifying checksum..." , fg = "blue" )
436
+ sha256_hash = hashlib .sha256 ()
437
+ with open (file_path , "rb" ) as f :
438
+ for byte_block in iter (lambda : f .read (4096 ), b"" ):
439
+ sha256_hash .update (byte_block )
440
+
441
+ with open (checksum_path ) as f :
442
+ expected_checksum = f .read ().strip ()
443
+
444
+ if sha256_hash .hexdigest () != expected_checksum :
445
+ raise Exception ("Checksum verification failed!" )
446
+ click .secho (" Checksum verified." , fg = "blue" )
447
+
448
+
449
+ def install_helm_to_venv (helm_bin_path ):
450
+ venv_bin_dir = os .path .join (sys .prefix , "bin" )
451
+ helm_dst_path = os .path .join (venv_bin_dir , HELM_BINARY_NAME )
452
+ shutil .move (helm_bin_path , helm_dst_path )
453
+ os .chmod (helm_dst_path , 0o755 )
454
+ click .secho (f" { HELM_BINARY_NAME } installed into { helm_dst_path } " , fg = "blue" )
455
+
456
+
457
+ def install_helm_rootlessly_to_venv ():
458
+ if not is_in_virtualenv ():
459
+ click .secho (
460
+ "Error: You are not in a virtual environment. Please activate a virtual environment and try again." ,
461
+ fg = "yellow" ,
462
+ )
463
+ sys .exit (1 )
464
+
465
+ version = get_latest_version_of_helm ()
466
+ if version is None :
467
+ click .secho (
468
+ "Error: Could not fetch the latest version of Helm. Please check your internet connection." ,
469
+ fg = "yellow" ,
470
+ )
471
+ sys .exit (1 )
472
+
473
+ os_name = get_os_name_for_helm ()
474
+ if os_name is None :
475
+ click .secho (
476
+ "Error: Could not determine the operating system of this computer." , fg = "yellow"
477
+ )
478
+ sys .exit (1 )
479
+
480
+ arch = os .uname ().machine
481
+ arch_map = {"x86_64" : "amd64" , "i386" : "386" , "aarch64" : "arm64" , "armv7l" : "arm" }
482
+ arch = arch_map .get (arch , arch )
483
+
484
+ helm_filename = f"{ HELM_BINARY_NAME } -{ version } -{ os_name } -{ arch } .tar.gz"
485
+ helm_url = f"{ HELM_DOWNLOAD_URL_STUB } { helm_filename } "
486
+ checksum_url = f"{ helm_url } .sha256"
487
+
488
+ try :
489
+ with tempfile .TemporaryDirectory () as temp_dir :
490
+ helm_archive_path = os .path .join (temp_dir , helm_filename )
491
+ checksum_path = os .path .join (temp_dir , f"{ helm_filename } .sha256" )
492
+
493
+ download_file (helm_url , helm_archive_path )
494
+ download_file (checksum_url , checksum_path )
495
+ verify_checksum (helm_archive_path , checksum_path )
496
+
497
+ # Extract Helm and install it in the virtual environment's bin folder
498
+ with tarfile .open (helm_archive_path , "r:gz" ) as tar :
499
+ tar .extractall (path = temp_dir )
500
+ helm_bin_path = os .path .join (temp_dir , os_name + "-" + arch , HELM_BINARY_NAME )
501
+ install_helm_to_venv (helm_bin_path )
502
+
503
+ click .secho (
504
+ f" { HELM_BINARY_NAME } { version } installed successfully to your virtual environment!\n " ,
505
+ fg = "blue" ,
506
+ )
507
+
508
+ except Exception as e :
509
+ click .secho (f"Error: { e } \n Could not install helm." , fg = "yellow" )
510
+ sys .exit (1 )
0 commit comments