@@ -134,17 +134,6 @@ def generate_password():
134134 return password
135135
136136
137- def write_credentials_file (folder : pathlib .Path , product , lines ):
138- file_path = folder .joinpath (CREDENTIALS_FILE .format (product ))
139- try :
140- with open (file_path , "w" ) as file :
141- file .writelines ([f"{ text } \n " for text in lines ])
142- except Exception :
143- pass
144- else :
145- CONSOLE .msg (f"(Credentials also written to { file_path .name } file)" )
146-
147-
148137def delete_file (file_path ):
149138 LOG .debug ("Deleting [%s]" , file_path .name )
150139 file_path .unlink (missing_ok = True )
@@ -245,22 +234,6 @@ def msg(self, text, skip_logging=False):
245234 print (text )
246235 self ._last_is_space = False
247236
248- def __enter__ (self ):
249- print (self .MARGIN , end = "" )
250- return self
251-
252- def send (self , text ):
253- print (text , end = "" )
254- sys .stdout .flush ()
255- self ._partial_msg += text
256-
257- def __exit__ (self , exc_type , exc_val , exc_tb ):
258- print ("" )
259- LOG .info ("Console message: [%s]" , self ._partial_msg )
260- self ._partial_msg = ""
261- self ._last_is_space = False
262- return False
263-
264237 def print_log (self , log_path : pathlib .Path ) -> None :
265238 with log_path .open () as log_file :
266239 print ("" )
@@ -271,6 +244,43 @@ def print_log(self, log_path: pathlib.Path) -> None:
271244 print ("" )
272245 self ._last_is_space = True
273246
247+ @contextlib .contextmanager
248+ def partial (self ):
249+ print (self .MARGIN , end = "" )
250+
251+ def console_partial (text ):
252+ print (text , end = "" )
253+ sys .stdout .flush ()
254+ self ._partial_msg += text
255+
256+ try :
257+ yield console_partial
258+ finally :
259+ print ("" )
260+ LOG .info ("Console message: [%s]" , self ._partial_msg )
261+ self ._partial_msg = ""
262+ self ._last_is_space = False
263+
264+ @contextlib .contextmanager
265+ def tee (self , file_path , append = False ):
266+ tee_lines = ["" if append else None ]
267+
268+ def console_tee (text , skip_logging = False ):
269+ tee_lines .append (text )
270+ return self .msg (text , skip_logging = skip_logging )
271+
272+ self .space ()
273+
274+ try :
275+ yield console_tee
276+ finally :
277+ self .space ()
278+ try :
279+ with open (file_path , "a" if append else "w" ) as file :
280+ file .writelines ([f"{ text } \n " for text in tee_lines if text is not None ])
281+ except Exception :
282+ LOG .exception ("Error tee'ing content to %s" , file_path )
283+
274284
275285CONSOLE = Console ()
276286
@@ -779,25 +789,25 @@ def execute(self, args):
779789 action_fail_step = None
780790 for step in action_steps :
781791 executed_steps .append (step )
782- with CONSOLE :
783- CONSOLE . send (f"{ step .label } ... " )
792+ with CONSOLE . partial () as partial :
793+ partial (f"{ step .label } ... " )
784794 try :
785795 if action_fail_exception :
786796 raise SkipStep
787797 LOG .debug ("Executing step [%s]" , step )
788798 step .execute (self , args )
789799 except SkipStep :
790- CONSOLE . send ("SKIPPED" )
800+ partial ("SKIPPED" )
791801 continue
792802 except Exception as e :
793- CONSOLE . send ("FAILED" )
803+ partial ("FAILED" )
794804 if step .required :
795805 action_fail_exception = e
796806 action_fail_step = step
797807 else :
798808 LOG .warning ("Non-required step [%s] failed with: %s" , step , e )
799809 else :
800- CONSOLE . send ("OK" )
810+ partial ("OK" )
801811
802812 if action_fail_exception :
803813 CONSOLE .title (f"{ self .label } FAILED" )
@@ -1247,11 +1257,12 @@ def on_action_success(self, action, args):
12471257 if args .namespace != NAMESPACE :
12481258 cmd_args .append (f"--namespace={ args .namespace } " )
12491259
1250- CONSOLE .space ()
1251- CONSOLE .msg ("Because you are using the docker driver on a Mac or Windows, you have to run" )
1252- CONSOLE .msg ("the following command in order to be able to access the platform." )
1253- CONSOLE .space ()
1254- CONSOLE .msg (f"python3 { INSTALLER_NAME } { args .prod } expose { ' ' .join (cmd_args )} " )
1260+ cred_file_path = action .data_folder .joinpath (CREDENTIALS_FILE .format (args .prod ))
1261+ with CONSOLE .tee (cred_file_path , append = True ) as console_tee :
1262+ console_tee ("Because you are using the docker driver on a Mac or Windows, you have to run" )
1263+ console_tee ("the following command in order to be able to access the platform." )
1264+ console_tee ("" )
1265+ console_tee (f"python3 { INSTALLER_NAME } { args .prod } expose { ' ' .join (cmd_args )} " )
12551266
12561267 self ._collect_images_sha (action , args )
12571268
@@ -1309,25 +1320,17 @@ def execute(self, action, args):
13091320 )
13101321
13111322 def on_action_success (self , action , args ):
1312- info_lines = []
1313- if url := action .ctx .get ("base_url" ):
1314- for service , label in SERVICES_LABELS .items ():
1315- info_lines .append (f"{ label :>20} : { SERVICES_URLS [service ].format (url )} " )
1316- info_lines .append ("" )
1317-
1318- info_lines .extend (
1319- [
1320- f"Username: { self ._user_data ['username' ]} " ,
1321- f"Password: { self ._user_data ['password' ]} " ,
1322- "" ,
1323- ]
1324- )
1323+ cred_file_path = action .data_folder .joinpath (CREDENTIALS_FILE .format (args .prod ))
1324+ with CONSOLE .tee (cred_file_path ) as console_tee :
1325+ if url := action .ctx .get ("base_url" ):
1326+ for service , label in SERVICES_LABELS .items ():
1327+ console_tee (f"{ label :>20} : { SERVICES_URLS [service ].format (url )} " )
1328+ console_tee ("" )
13251329
1326- CONSOLE .space ()
1327- for line in info_lines :
1328- CONSOLE .msg (line , skip_logging = "Password" in line ) if line else CONSOLE .space ()
1330+ console_tee (f"Username: { self ._user_data ['username' ]} " )
1331+ console_tee (f"Password: { self ._user_data ['password' ]} " , skip_logging = True )
13291332
1330- write_credentials_file ( action . data_folder , args . prod , info_lines )
1333+ CONSOLE . msg ( f"(Credentials also written to { cred_file_path . name } file)" )
13311334
13321335
13331336class ObsGenerateDemoConfigStep (Step ):
@@ -1690,7 +1693,7 @@ def execute(self, action, args):
16901693
16911694 contents = action .docker_compose_file_path .read_text ()
16921695 if self .update_version :
1693- contents = re .sub (r"(image:\s*datakitchen.+:).+\n" , fr "\1{ TESTGEN_LATEST_TAG } \n" , contents )
1696+ contents = re .sub (r"(image:\s*datakitchen.+:).+\n" , rf "\1{ TESTGEN_LATEST_TAG } \n" , contents )
16941697
16951698 if self .update_analytics :
16961699 if args .send_analytics_data :
@@ -1713,7 +1716,7 @@ def execute(self, action, args):
17131716 if self .update_token :
17141717 match = re .search (r"^([ \t]+)TG_METADATA_DB_HOST:.*$" , contents , flags = re .M )
17151718 var = f"\n { match .group (1 )} TG_JWT_HASHING_KEY: { str (base64 .b64encode (random .randbytes (32 )), 'ascii' )} "
1716- contents = contents [0 : match .end ()] + match .group (1 ) + var + contents [match .end ():]
1719+ contents = contents [0 : match .end ()] + match .group (1 ) + var + contents [match .end () :]
17171720
17181721 action .docker_compose_file_path .write_text (contents )
17191722
@@ -1773,17 +1776,15 @@ def on_action_success(self, action, args):
17731776 CONSOLE .msg (f"Created new { DOCKER_COMPOSE_FILE } file using image { args .image } " )
17741777
17751778 protocol = "https" if args .ssl_cert_file and args .ssl_key_file else "http"
1776- info_lines = [
1777- f"User Interface: { protocol } ://localhost:{ args .port } " ,
1778- "CLI Access: docker compose exec engine bash" ,
1779- "" ,
1780- f"Username: { self .username } " ,
1781- f"Password: { self .password } " ,
1782- ]
1783- CONSOLE .space ()
1784- for line in info_lines :
1785- CONSOLE .msg (line , skip_logging = "Password" in line )
1786- write_credentials_file (action .data_folder , args .prod , info_lines )
1779+ cred_file_path = action .data_folder .joinpath (CREDENTIALS_FILE .format (args .prod ))
1780+ with CONSOLE .tee (cred_file_path ) as console_tee :
1781+ console_tee (f"User Interface: { protocol } ://localhost:{ args .port } " )
1782+ console_tee ("CLI Access: docker compose exec engine bash" )
1783+ console_tee ("" )
1784+ console_tee (f"Username: { self .username } " )
1785+ console_tee (f"Password: { self .password } " )
1786+
1787+ CONSOLE .msg (f"(Credentials also written to { cred_file_path .name } file)" )
17871788
17881789 def on_action_fail (self , action , args ):
17891790 # We keep the file around for inspection when in debug mode
@@ -1834,7 +1835,7 @@ def create_compose_file(self, action, args, username, password, ssl_cert_file, s
18341835 TESTGEN_PASSWORD: { password }
18351836 TG_DECRYPT_SALT: { generate_password ()}
18361837 TG_DECRYPT_PASSWORD: { generate_password ()}
1837- TG_JWT_HASHING_KEY: { str (base64 .b64encode (random .randbytes (32 )), ' ascii' )}
1838+ TG_JWT_HASHING_KEY: { str (base64 .b64encode (random .randbytes (32 )), " ascii" )}
18381839 TG_METADATA_DB_HOST: postgres
18391840 TG_TARGET_DB_TRUST_SERVER_CERTIFICATE: yes
18401841 TG_EXPORT_TO_OBSERVABILITY_VERIFY_SSL: no
@@ -2378,6 +2379,19 @@ def execute(self, args):
23782379 raise AbortAction
23792380
23802381
2382+ class AccessInstructionsAction (Action ):
2383+ args_cmd = "access-info"
2384+
2385+ def execute (self , args ):
2386+ try :
2387+ info = self .data_folder .joinpath (CREDENTIALS_FILE .format (args .prod )).read_text ()
2388+ except Exception :
2389+ CONSOLE .msg ("No Access Information found. Is the platform installed?" )
2390+ else :
2391+ for line in info .splitlines ():
2392+ CONSOLE .msg (line )
2393+
2394+
23812395#
23822396# Entrypoint
23832397#
@@ -2401,6 +2415,7 @@ def run_installer(args):
24012415 tg_menu = Menu (run_installer , "TestGen" )
24022416 tg_menu .add_option ("Install TestGen" , ["tg" , "install" ])
24032417 tg_menu .add_option ("Upgrade TestGen" , ["tg" , "upgrade" ])
2418+ tg_menu .add_option ("Access Instructions" , ["tg" , "access-info" ])
24042419 tg_menu .add_option ("Install TestGen demo data" , ["tg" , "run-demo" ])
24052420 tg_menu .add_option (
24062421 "Install TestGen demo data with Observability export" ,
@@ -2412,10 +2427,12 @@ def run_installer(args):
24122427 obs_menu = Menu (run_installer , "Observability" )
24132428 obs_menu .add_option ("Install Observability" , ["obs" , "install" ])
24142429 obs_menu .add_option ("Upgrade Observability" , ["obs" , "upgrade" ])
2430+ obs_menu .add_option ("Access Instructions" , ["obs" , "access-info" ])
2431+ obs_menu .add_option ("Expose web access" , ["obs" , "expose" ])
24152432 obs_menu .add_option ("Install Observability demo data" , ["obs" , "run-demo" ])
24162433 obs_menu .add_option ("Delete Observability demo data" , ["obs" , "delete-demo" ])
24172434 obs_menu .add_option ("Run heartbeat demo" , ["obs" , "run-heartbeat-demo" ])
2418- obs_menu .add_option ("Delete Observability" , ["obs" , "delete" ])
2435+ obs_menu .add_option ("Uninstall Observability" , ["obs" , "delete" ])
24192436
24202437 cfg_menu = Menu (add_config , "Configuration" )
24212438 cfg_menu .add_option (
@@ -2445,6 +2462,7 @@ def get_installer_instance():
24452462 [
24462463 ObsInstallAction (),
24472464 ObsExposeAction (),
2465+ AccessInstructionsAction (),
24482466 ObsDeleteAction (),
24492467 ObsRunDemoAction (),
24502468 ObsDeleteDemoAction (),
@@ -2457,6 +2475,7 @@ def get_installer_instance():
24572475 [
24582476 TestgenInstallAction (),
24592477 TestgenUpgradeAction (),
2478+ AccessInstructionsAction (),
24602479 TestgenDeleteAction (),
24612480 TestgenRunDemoAction (),
24622481 TestgenDeleteDemoAction (),
0 commit comments